This notebook contains code and output from exploring the data to inform cleaning steps and analysis process.

library(tidyverse)
library(ggplot2)
library(lubridate)
library(GGally) # for ggpairs to explore correlations
library(psych) # for pairplots to explore correlations

Load and inspect data

Daily energy data

Aug 21

Dataset 1, from https://www.kaggle.com/datasets/emmanuelfwerr/london-homes-energy-data.

daily_energy <- read_csv("../3_raw_data/london_energy.csv") %>% 
  janitor::clean_names()
Rows: 3510433 Columns: 3── Column specification ─────────────────────────────────────────────────────────────
Delimiter: ","
chr  (1): LCLid
dbl  (1): KWH
date (1): Date
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
skimr::skim(daily_energy)
── Data Summary ────────────────────────
                           Values      
Name                       daily_energy
Number of rows             3510433     
Number of columns          3           
_______________________                
Column type frequency:                 
  character                1           
  Date                     1           
  numeric                  1           
________________________               
Group variables            None        

3510433 rows x 3 cols

Rows are one date and one household value. 5,566 households over 829 days (2011-11-23 to 2014-02-28)

No missing values in kaggle aggregated energy data

Aug 23

BUT there are missing values because only households with values on a date are included, not every household has values for every date in range. See below section on CLEANING household data.

# check for duplicates -- NONE
daily_energy %>% 
  group_by(lc_lid, date) %>% 
  summarise(count = n()) %>% 
  filter(count > 1)
`summarise()` has grouped output by 'lc_lid'. You can override using the `.groups` argument.

No duplicates.

Energy consumption patterns over all households

averaging daily energy consumption (across all households)

Aug 21

Not sure if mean is appropriate measure here, let’s look at histogram of energy consumption values of every household on a single date:

# boxplot on 2012-12-31
daily_energy %>%
  filter(date == "2012-12-31") %>% 
  ggplot(aes(x = kwh)) + 
  geom_boxplot()


# histogram on 2012-12-31
daily_energy %>%
  filter(date == "2012-12-31") %>% 
  ggplot() + 
  geom_histogram(aes(x = kwh), bins = 150)

Energy consumption on an individual date, frequency of households, is not normally distributed but right-skewed. Median may be a better average daily measure than mean.

simple lines

Aug 21

# median
daily_energy %>% 
  group_by(date) %>% 
  mutate(median_kwh = median(kwh)) %>% 
  ggplot(aes(x = date, y = median_kwh)) +
  geom_line()

daily_energy %>% 
  group_by(date) %>% 
  mutate(mean_kwh = mean(kwh)) %>% 
  ggplot(aes(x = date, y = mean_kwh)) +
  geom_line()

Definitely some seasonality in energy consumption, whichever average used.

Rolling average

August 22

See classnotes wk7 day 1 (new notes in codeclan_work/flipped… folder) - use package slider

First need to convert data to tsibble:

library(tsibble)
library(slider)
# convert data to tsibble
daily_energy_ts <- as_tsibble(daily_energy, key = lc_lid, index = date)
index_var(daily_energy_ts)
key_vars(daily_energy_ts)

To make rolling average for whole dataset, first find median of all values on a each date (because values are not normally distributed on a single date in December, see above, skewed), then do mean across a 14-day window (set as 7 before, 6 after, only show for complete range).

# make df with median for each day
median_all_daily_energy_ts <- daily_energy_ts %>% 
  index_by(date) %>% 
  summarise(median_kwh = median(kwh))
# calc rolling 14-day mean
median_all_daily_energy_ts <- median_all_daily_energy_ts %>% 
  mutate(kwh_moving_avg = slide_dbl(
    .x = median_kwh,
    .f = ~ mean(.),
    .before = 7,
    .after = 6, # 14-day rolling average
    .complete = TRUE
  ))
median_all_daily_energy_ts %>% 
  ggplot() + 
  geom_line(aes(x = date, y = median_kwh), colour = "grey") + 
  geom_line(aes(x = date, y = kwh_moving_avg), colour = "red") +
  theme_minimal()

Need to look at seasonal decomposition.

Seasonal decomposition

Aug 22

library(feasts)

Use the median values

median_all_daily_energy_ts %>% 
  autoplot(median_kwh) +
  xlab("Date") + ylab("Median daily energy consumption (kWh)")

Interpret:

  • looks like higher energy usage in winter months (Dec-Feb), lower in summer months (Jun-Aug) with increase/decrease in between
  • this pattern repeats over the 2.5 years data we have available (3 winters, 2 summers)
  • there is fluctuation within these times too - a weekly pattern?
# look at seasonal pattern
median_all_daily_energy_ts %>% 
  gg_season(median_kwh)

Seasonal pattern is consistent year-on-year. Maybe 2014 looks like slightly lower usage? Also note some higher variability in winter 2011 data. Need to remove last date because values are off? And first date too?

trend: weekdays

Look at subseries:

Can see trend for every weekday - does look like Sat & Sun are higher.

median_all_daily_energy_ts %>% 
  gg_subseries(y = median_kwh, period = 7)

restrict to winter only?

old code, didn’t work, not sure the Q is right so pause this for now

daily_energy_ts %>% 
  mutate(quarter = quarter(date, type = "date_first", fiscal_start = 12)) %>% 
  arrange(quarter) %>% 
  index_by(lc_lid) %>% 
  summarise(median_kwh = median(kwh))
Error in `validate_index()`:
! Unsupported index type: character
Backtrace:
 1. ... %>% summarise(median_kwh = median(kwh))
 4. tsibble:::index_by.tbl_ts(., lc_lid)
 5. tsibble::build_tsibble(...)
 6. tsibble:::validate_index(tbl, !!index2)

When looking for subseries, first change index to season (using quarter) - this doesn’t work for subseries, need it to be date format

quarterly_energy_ts <- daily_energy_ts %>% 
  # make quarters where Dec is in Q1 of year (adjusted as season not real quarter)
  mutate(quarter = quarter(date, type = "date_first", fiscal_start = 12)) %>% 
  update_tsibble(key = lc_lid, index = quarter)
Error in `validate_tsibble()`:
! A valid tsibble must have distinct rows identified by key and index.
ℹ Please use `duplicates()` to check the duplicated rows.
Backtrace:
 1. ... %>% update_tsibble(key = lc_lid, index = quarter)
 2. tsibble::update_tsibble(., key = lc_lid, index = quarter)
 3. tsibble::build_tsibble(...)
 4. tsibble:::validate_tsibble(data = tbl, key = key_vars, key_data = key_data, index = index)

Energy consumption patterns within households

Individal households

Aug 21

Let’s look at a few individual households:

daily_energy %>% 
  distinct(lc_lid)
daily_energy %>% 
  filter(lc_lid %in% c("MAC000002", "MAC000058", "MAC000359", "MAC000714", "MAC000933", "MAC000997")) %>% 
  ggplot(aes(x = date, y = kwh, colour = lc_lid)) +
  geom_line() +
  facet_wrap(~lc_lid)

Can already see households are behaving differently, some high consumers, others less so; some change over the year, some less so.

Might want to use a rolling average here, e.g. 7-day, to give a smoother line (as above!)

Might also want to consider if there are “types” of household to split the data by, e.g. according to how variable they are in a year, their level of consumption (high/low) - see more below.

types of household (by consumption patterns)

Aug 21

look to see if there are obvious high/medium/low consumer groups

household_alltime_consumption <- daily_energy %>% 
  group_by(lc_lid) %>% 
  summarise(alltime_median_kwh = median(kwh),
            alltime_mean_kwh = mean(kwh))
household_alltime_consumption
household_alltime_consumption %>% 
  ggplot() +
  geom_histogram(aes(x = alltime_median_kwh), bins = 200)

Look at january or july dates only, when there may be more variation in usage (cold and hot weather)

household_jan_jul_consumption <- daily_energy %>% 
  mutate(month = month(date, label = TRUE, abbr = FALSE)) %>% 
  filter(month %in% c("January","July")) %>% 
  group_by(lc_lid, month) %>% 
  summarise(median_kwh = median(kwh),
            mean_kwh = mean(kwh))
`summarise()` has grouped output by 'lc_lid'. You can override using the `.groups` argument.
# january medians
household_jan_jul_consumption %>% 
  filter(month == "January") %>% 
  ggplot() +
  geom_histogram(aes(x = median_kwh), bins = 200)


# july medians
household_jan_jul_consumption %>% 
  filter(month == "July") %>% 
  ggplot() +
  geom_histogram(aes(x = median_kwh), bins = 200)

Most households have median daily consumption in range 0-30kWh in January and 0-20 kWh in July, indicating energy usage is highest in winter

household_jan_jul_consumption %>% 
  ggplot() +
  geom_boxplot(aes(x = month, y = median_kwh))

Maybe energy consumption in January is slightly more than in July. Could test this. Long tail of high energy consumers.

No obvious groups to split into, but could label households above Q3 as “high consumers”, below Q1 as “low consumers”, and rest as “medium consumers”.

all_kwh_stats <- daily_energy %>% 
  select(kwh) %>% 
  skimr::skim()

all_kwh_stats %>% 
  colnames()
 [1] "skim_type"     "skim_variable" "n_missing"     "complete_rate" "numeric.mean" 
 [6] "numeric.sd"    "numeric.p0"    "numeric.p25"   "numeric.p50"   "numeric.p75"  
[11] "numeric.p100"  "numeric.hist" 
kwh_q1 <- all_kwh_stats %>% 
  select(numeric.p25) %>% 
  pull()

kwh_q3 <- all_kwh_stats %>% 
  select(numeric.p75) %>% 
  pull()

kwh_q1
[1] 4.685
kwh_q3
[1] 12.576

Note these are calculated from all dates, not adjusted for jan/july/seasonality and note the date range covers more winters (3) than summers (2).

# add household type, can be used as lookup key-value (join with full data)
household_alltime_consumption <- household_alltime_consumption %>% 
  mutate(household_consumption_level = case_when(
    alltime_median_kwh >= kwh_q3 ~ "high",
    alltime_median_kwh > kwh_q1 ~ "average",
    alltime_median_kwh <= kwh_q1 ~ "low",
    .default = NA_character_
  ),
  household_consumption_level = factor(household_consumption_level, levels = c("low", "average", "high")))
household_alltime_consumption %>% 
  ggplot() +
  geom_boxplot(aes(x = household_consumption_level, y = alltime_median_kwh))

household_alltime_consumption %>% 
  summarise(count = n(), .by = household_consumption_level)

Seems like a potentially useful split of data

daily_energy_processed <- left_join(daily_energy, household_alltime_consumption, by = "lc_lid")

daily_energy_processed

Look at median energy over time by type:

daily_energy_processed_medians <- daily_energy_processed %>% 
  group_by(date, household_consumption_level) %>% 
  summarise(median_kwh = median(kwh)) 
`summarise()` has grouped output by 'date'. You can override using the `.groups` argument.
daily_energy_processed_medians %>% 
  arrange(date)

daily_energy_processed_medians %>% 
  ggplot() +
  geom_line(aes(x = date, y = median_kwh, colour = household_consumption_level))

There may be a way to group households by consumption in an unsupervised way, i.e. by clustering instead.. tbc

finding example households

Aug 23

CLEAN: missing data and zero values
# find households with the 10 lowest kwh scores
daily_energy_processed %>% 
  arrange(kwh) %>% 
  head(10)

Arranging by kwh low to high shows lots of zeros – investigate:

hholds_zero_values <- daily_energy_processed %>% 
  filter(kwh == 0) %>%  # 15,168 rows
  summarise(count = n(), .by = lc_lid) %>% 
  arrange(desc(count))

hholds_zero_values

308 households with 0 values.

hholds_zero_values %>% 
  ggplot() +
  geom_col(aes(x = lc_lid, y = count))
hholds_with_zeros <- hholds_zero_values %>% 
  pull(lc_lid)

daily_energy_processed %>% 
  filter(lc_lid %in% hholds_with_zeros) %>% 
  mutate(zero_kwh_value = if_else(kwh == 0, T, F), .before = kwh) %>% 
  ggplot() +
  geom_line(aes(x = date, y = zero_kwh_value, group = lc_lid), alpha = 0.2, colour = "grey")
daily_energy_processed %>% 
  filter(lc_lid %in% hholds_with_zeros) %>% 
  mutate(zero_kwh_value = if_else(kwh == 0, T, F), .before = kwh) %>% 
  ggplot() +
  geom_point(aes(x = date, y = zero_kwh_value), colour = "grey", alpha = 0.1)

Maybe some households weren’t collecting data / didn’t join until later in the period.

daily_energy_processed %>% 
  filter(date > "2013-01-01" & kwh == 0) # still 10,500 zeroes
daily_energy_processed %>% 
  distinct(date)

829 days

Note no households are all zero values though, highest number of zeroes is 789 days.

Could drop households that have a zero value for more than 40% days in the time range, since we are interested in estimating/understanding energy usage, and zero kwh indicates something else is going on here.

0.4*829

So: Remove the 10 hholds with >= 331 zero values (i.e 40% or more days with 0 kwh):

missing_data_hholds <- daily_energy_processed %>% 
  filter(kwh == 0) %>%  # 15,168 rows
  summarise(count = n(), .by = lc_lid) %>% 
  filter(count >= 331) %>% 
  pull(lc_lid)

missing_data_hholds

Could impute with each household’s median kwh value for that month and if weekday or weekend. OR could drop these households (remember we have 5500!)

Ideally, try both approaches and see which gives better result

So, daily_energy_trim has 10 fewer households (5,557?)

Check weekdays: * 28 feb 2014 was a friday - yes * 12 oct 2012 was also a friday - yes * check yearseason arranges in correct order - yes

daily_energy_trim %>% 
  filter(kwh == 0) # now 9,633 zero values
# impute missing kwh values with median value for that hhold that yearseason and wday type
daily_energy_imputed <- daily_energy_trim %>% 
  mutate(zero_kwh_value = if_else(kwh == 0, T, F), .before = kwh) %>% 
  group_by(lc_lid, yearseason, is_weekend) %>% 
  mutate(hh_avg_season_wday = mean(kwh), .after = kwh) %>% 
  ungroup() %>% 
  mutate(kwh_imputed = if_else(kwh == 0, hh_avg_season_wday, kwh), .before = kwh)
daily_energy_imputed  %>% 
  filter(zero_kwh_value == T) # 9,633 values needed imputing 

daily_energy_imputed  %>% 
  filter(zero_kwh_value == T & kwh_imputed == 0)
# using median: 4,648 values remain
# using mean: 807 zero values remain


## before removing the 10 hholds with 40%+ zero days
# 15,168 values were zero
# using median: 10,037 zero values remain after trying to impute with median for wday & season per hhold
# using mean: 3,195 zero values remain - use mean!

So daily_energy_imputed cleaning steps: * remove 10 households with 40%+ days with zero kWh – these are unusual cases, maybe indicate getting energy from elsewhere, or properties not being lived in much, or something not working with their smart meter * impute remaining zeroes using the mean kwh for that household in that season and according to weekday/weekend type * 807 zero values remain in the data, leave them in, low % of the total 3,503,867 observations (0.02%)

100*807/nrow(daily_energy_imputed)

what does the data look like now for a household with some zeroes imputed?

“MAC002050” had 316 zeroes, so not removed. “MAC002072” had 303.

# check households with lots of zero values MAC000197, MAC004067, MAC000037
daily_energy_imputed %>% 
  filter(lc_lid %in% c("MAC002050", "MAC002072")) %>%
  ggplot(aes(x = date, group = lc_lid)) +
  geom_line(aes(y = kwh), colour = "blue", linetype = 1) +
  geom_line(aes(y = kwh_imputed), colour = "red", linetype = 1) +
  facet_wrap(~ lc_lid, ncol = 1)

Actually, there are whole periods of zeroes – these should also be removed

hholds_zero_values %>% 
  filter(!lc_lid %in% missing_data_hholds) # filter for hholds not already removed

e.g. MAC003707, MAC002771 have 100 zeroes; MAC001655 has 78 zeroes; MAC004756 has 41

daily_energy_imputed %>% 
  filter(lc_lid %in% c("MAC003707", "MAC002771", "MAC001655", "MAC004756")) %>%
  ggplot(aes(x = date, group = lc_lid)) +
  geom_line(aes(y = kwh), colour = "blue", linetype = 1) +
  geom_line(aes(y = kwh_imputed), colour = "red", linetype = 1) +
  facet_wrap(~ lc_lid, ncol = 1)
  • MAC001655 has small periods of zeroes every now and then - possible these are holiday / non-residential periods when they turn everything off, looks like a real scenario, imputations don’t look bad for the data
  • MAC002771 has a whole chunk of zeros, and low values throughout
  • MAC003707 – chunks of zero values, does not look reliable
  • MAC004756 – clearly missing lots of data after a point, should have more than that number of 0s…
restart CLEANING

Check that each household has full 829 days

daily_energy_processed %>% 
  group_by(lc_lid) %>% 
  summarise(count = n()) %>% 
  filter(count < 829)

5,555 of the 5,566 households do not have complete data for the whole time period! i.e. only 11 households have data for the whole time.

What is the threshold at which I don’t include a household?

Reasonably, a year’s worth of data is sufficient for modelling weather and energy interactions…

hholds_lt1y <- daily_energy_processed %>% 
  group_by(lc_lid) %>% 
  summarise(count = n()) %>% 
  filter(count < 365) %>% 
  pull(lc_lid)

hholds_lt1y

182 households have less than 365 days’ worth of data, in the 829-day period.

daily_energy_processed %>% 
  filter(!lc_lid %in% hholds_lt1y) %>% 
  filter(kwh == 0) %>%  # 14,019 zero kWh values
  summarise(num_zeros = n(), .by = lc_lid) %>% 
  arrange(desc(num_zeros)) # 246 hholds have zero values

Repeat the cleaning steps above

hholds_zero_values2 <- daily_energy_processed %>% 
  filter(!lc_lid %in% hholds_lt1y) %>% 
  filter(kwh == 0) %>%  # 14,019 zero kWh values
  summarise(num_zeros = n(), .by = lc_lid) %>% 
  arrange(desc(num_zeros))

hholds_zero_values2

308 households with 0 values, 246 if remove the hholds with less than 365 days’ data

hholds_zero_values2 %>% 
  filter(num_zeros <= 30)

164 hholds have 30 or fewer zero values

total_days = as.numeric(max(daily_energy$date) - min(daily_energy$date) + 1)

hhold_stats <- daily_energy_processed %>% 
  mutate(zero_kwh = if_else(kwh == 0, T, F)) %>% 
  group_by(lc_lid) %>%
  summarise(num_values = n(),
            num_zeros = sum(zero_kwh),
            min_value = min(kwh),
            max_value = max(kwh),
            range = range(kwh),
            median_kwh = median(kwh),
            iqr = IQR(kwh),
            mean_kwh = mean(kwh),
            sd = sd(kwh)
            ) %>% 
  mutate(pc_missing = (100 * (total_days - num_values) / total_days), .after = lc_lid) %>% 
  mutate(pc_zeros = (100 * num_zeros / num_values), .after = pc_missing) %>% 
  ungroup()
Warning: Returning more (or less) than 1 row per `summarise()` group was deprecated in dplyr 1.1.0.
Please use `reframe()` instead.
When switching from `summarise()` to `reframe()`, remember that `reframe()` always returns an ungrouped data frame and adjust accordingly.`summarise()` has grouped output by 'lc_lid'. You can override using the `.groups` argument.
hhold_stats %>% 
  arrange(desc(pc_missing))
  
hhold_stats %>% 
  arrange(desc(pc_zeros))
hhold_stats %>% 
  ggplot() +
  geom_point(aes(x = pc_missing, y = pc_zeros))

hhold_stats %>% 
  ggplot() +
  geom_histogram(aes(x = pc_missing), bins = 100)

hhold_stats %>% 
  ggplot() +
  geom_histogram(aes(x = pc_zeros), bins = 100)

hhold_stats %>% 
  filter(pc_zeros == 100)
hhold_stats %>% 
  filter(pc_zeros > 10)

146 households with > 10% values as 0 kWh

0 kWh could be real, e.g. on holiday/out of house and everything switched off, or even if have solar panels and switch to only using that on sunny days (could look at high sun low energy days)

But overall, lots of 0 values is not useful for modelling change in energy usage, most households don’t have so many zeroes - so just remove these households, can’t explain them right now

hholds_10pc_zero <- hhold_stats %>% 
  filter(pc_zeros > 10) %>% 
  pull(lc_lid)

hholds_10pc_zero
  [1] "MAC000037" "MAC000037" "MAC000134" "MAC000134" "MAC000197"
  [6] "MAC000197" "MAC000242" "MAC000242" "MAC000287" "MAC000287"
 [11] "MAC000399" "MAC000399" "MAC000418" "MAC000418" "MAC000636"
 [16] "MAC000636" "MAC000895" "MAC000895" "MAC000912" "MAC000912"
 [21] "MAC001051" "MAC001051" "MAC001082" "MAC001082" "MAC001101"
 [26] "MAC001101" "MAC001150" "MAC001150" "MAC001181" "MAC001181"
 [31] "MAC001309" "MAC001309" "MAC001311" "MAC001311" "MAC001340"
 [36] "MAC001340" "MAC001348" "MAC001348" "MAC001456" "MAC001456"
 [41] "MAC001629" "MAC001629" "MAC001654" "MAC001654" "MAC001655"
 [46] "MAC001655" "MAC001777" "MAC001777" "MAC001898" "MAC001898"
 [51] "MAC001921" "MAC001921" "MAC001984" "MAC001984" "MAC002050"
 [56] "MAC002050" "MAC002072" "MAC002072" "MAC002096" "MAC002096"
 [61] "MAC002110" "MAC002110" "MAC002136" "MAC002136" "MAC002224"
 [66] "MAC002224" "MAC002374" "MAC002374" "MAC002388" "MAC002388"
 [71] "MAC002465" "MAC002465" "MAC002564" "MAC002564" "MAC002594"
 [76] "MAC002594" "MAC002612" "MAC002612" "MAC002771" "MAC002771"
 [81] "MAC002842" "MAC002842" "MAC002863" "MAC002863" "MAC002873"
 [86] "MAC002873" "MAC002976" "MAC002976" "MAC003125" "MAC003125"
 [91] "MAC003156" "MAC003156" "MAC003207" "MAC003207" "MAC003246"
 [96] "MAC003246" "MAC003614" "MAC003614" "MAC003627" "MAC003627"
[101] "MAC003707" "MAC003707" "MAC003777" "MAC003777" "MAC003973"
[106] "MAC003973" "MAC004047" "MAC004047" "MAC004067" "MAC004067"
[111] "MAC004292" "MAC004292" "MAC004421" "MAC004421" "MAC004672"
[116] "MAC004672" "MAC004677" "MAC004677" "MAC004723" "MAC004723"
[121] "MAC004735" "MAC004735" "MAC004756" "MAC004756" "MAC004854"
[126] "MAC004854" "MAC004931" "MAC004931" "MAC004982" "MAC004982"
[131] "MAC005033" "MAC005033" "MAC005313" "MAC005313" "MAC005556"
[136] "MAC005556" "MAC005558" "MAC005558" "MAC005559" "MAC005559"
[141] "MAC005560" "MAC005560" "MAC005563" "MAC005563" "MAC005565"
[146] "MAC005565"
hhold_stats %>% 
  filter(!lc_lid %in% hholds_10pc_zero) %>% 
  filter(median_kwh == 0)

36 households with median of 0 –> indicates not useful data? None of these once the >10pc zero hholds removed

hholds_180obs <- hhold_stats %>% 
  filter(!lc_lid %in% hholds_10pc_zero) %>% 
  filter(num_values < 180) %>% 
  pull(lc_lid)

hholds_180obs
 [1] "MAC001278" "MAC001278" "MAC001300" "MAC001300" "MAC001477"
 [6] "MAC001477" "MAC001653" "MAC001653" "MAC001957" "MAC001957"
[11] "MAC002155" "MAC002155" "MAC003197" "MAC003197" "MAC003202"
[16] "MAC003202" "MAC003346" "MAC003346" "MAC003347" "MAC003347"
[21] "MAC003353" "MAC003353" "MAC003481" "MAC003481" "MAC003554"
[26] "MAC003554" "MAC003559" "MAC003559" "MAC003572" "MAC003572"
[31] "MAC003592" "MAC003592" "MAC003935" "MAC003935" "MAC004679"
[36] "MAC004679" "MAC005510" "MAC005510"

38 more households have fewer than 180 days’ worth of data, remove these. Can’t guarantee these are consecutive days, but 6 months’ worth of data should give enough variability in weather. Imagine making a model per household - 180 values would be enough I think.

Cleaning households data:

  • zero values:
    • remove 146 hholds with >10% zero values
    • what else would seem real versus unusual? could add a factor that is an indicator of num_zeros, in case need to remove a group
  • number of values (i.e. missing data):
    • remove those below a threshold: how much do we need in order to start characterising a household, maybe 180 days (6 months) worth? remove households with less than 180 observations
    • for remainder: could also add a factor that is indicator of amount of data for that household, in case need to factor in reliability or coverage

cleaning for specific dates

2014-02-28 has v low median value (at end of ts) - unusal, remove

daily_energy %>% 
  filter(date == "2014-02-28") # 4,987 obs, so most households
daily_energy %>% 
  filter(date == "2011-11-23") # start_date

Start date has only 13 households

Consider cleaning for dates that have minimum 50 households?

num_hholds_by_date <- daily_energy %>% 
  group_by(date) %>% 
  summarise(num_hholds = n())
num_hholds_by_date
num_hholds_by_date %>% 
  ggplot() +
  geom_line(aes(x = date, y = num_hholds))

Check for cleaned date that removed households with little data:

num_hholds_by_date_clean <- daily_energy_clean %>% 
  group_by(date) %>% 
  summarise(num_hholds = n())
num_hholds_by_date_clean
num_hholds_by_date_clean %>% 
  ggplot() +
  geom_line(aes(x = date, y = num_hholds))

What is minimal number of households to take a median from? 200?

That would remove 19 dates (i.e. start from 2011-12-12)

num_hholds_by_date_clean %>% 
  filter(num_hholds <200)

Or if start from 2011-12-01:

num_hholds_by_date_clean %>% 
  filter(date >= "2011-12-01") %>% 
  arrange(num_hholds)

Lowest value on a single day would be 91 households. That seems ok.

Start from 2011-12-01 - and also do this for weather_data.

household energy cleaning

Remember other processing I could add in:

  • daily_energy_trim code chunk has steps to add in date values like weekday, season, etc - useful for model too.
  • daily_energy_processed has computed alltime medians etc for comparison
  • daily_energy_imputed has remaining zeros imputed using median values for that season and weekday type – if zeroes seem unreal
  • daily_energy_ts - is a tsibble version
  • daily_energy_rolling_median - has 14-day moving average for plotting as timeseries
daily_energy_clean <- daily_energy %>% 
  filter(!lc_lid %in% hholds_10pc_zero) %>% 
  filter(!lc_lid %in% hholds_180obs) %>% 
  filter(date >= "2011-12-01" & date < "2014-02-28")
  # start in Winter 2011 (91 households by that point)
  # and remove last date, very low median value (unusual)

num_hholds_og <- length(unique(daily_energy$lc_lid))
num_hholds_clean <- length(unique(daily_energy_clean$lc_lid))

num_hholds_og
[1] 5566
num_hholds_clean
[1] 5474
num_hholds_og - num_hholds_clean
[1] 92
100* (num_hholds_og - num_hholds_clean) / num_hholds_og
[1] 1.652893

92 households (1.6%) removed from data, leaving 5,474 households in “cleaned” data.

Household stats

re-run household summary stats on clean
total_days = as.numeric(max(daily_energy$date) - min(daily_energy$date) + 1)

hhold_stats <- daily_energy_clean %>% 
  mutate(zero_kwh = if_else(kwh == 0, T, F)) %>% 
  group_by(lc_lid) %>%
  summarise(num_values = n(),
            num_zeros = sum(zero_kwh),
            min_value = min(kwh),
            max_value = max(kwh),
            range = range(kwh),
            median_kwh = median(kwh),
            iqr = IQR(kwh),
            mean_kwh = mean(kwh),
            sd = sd(kwh)
            ) %>% 
  mutate(pc_missing = (100 * (total_days - num_values) / total_days), .after = lc_lid) %>% 
  mutate(pc_zeros = (100 * num_zeros / num_values), .after = pc_missing) %>% 
  ungroup()
Warning: Returning more (or less) than 1 row per `summarise()` group was deprecated in dplyr 1.1.0.
Please use `reframe()` instead.
When switching from `summarise()` to `reframe()`, remember that `reframe()` always returns an ungrouped data frame and adjust accordingly.`summarise()` has grouped output by 'lc_lid'. You can override using the `.groups` argument.
hhold_stats %>% 
  arrange(desc(pc_missing))
  
hhold_stats %>% 
  arrange(desc(pc_zeros))
hhold_stats %>% 
  ggplot() + 
  geom_point(aes(x = pc_missing, y = pc_zeros))

hhold_stats %>% 
  ggplot() +
  geom_histogram(aes(x = pc_missing), bins = 100)

hhold_stats %>% 
  ggplot() +
  geom_histogram(aes(x = pc_zeros), bins = 100)

look at hhold stats
# median by range - profile of variance
hhold_stats %>% 
  filter(median_kwh != 0) %>% 
  ggplot() +
  geom_point(aes(x = median_kwh, y = iqr))

hhold_stats %>% 
  filter(median_kwh != 0) %>% 
  ggplot() +
  geom_point(aes(x = max_value, y = median_kwh))

Doesn’t look like groups

hhold_stats %>% 
  filter(median_kwh != 0) %>% 
  ggplot() +
  geom_point(aes(x = max_value, y = min_value))

Does look like there may be correlations, and maybe some groups could be made here

  • low min & max
  • low min & higher max (e.g. > 100)
  • low max & higher min (e.g. > 2) not this one
  • higher min & max

todo could look at ratio of max/min as delta_change

# check for min of 0
hhold_stats %>% 
  filter(min_value == 0) # 454 rows
# check for max of 0
hhold_stats %>% 
  filter(max_value == 0)
# 0 households

Calculate change as (max-min) or range / max (because max != 0)

hhold_stats %>% 
  mutate(delta = range / max_value) %>% 
  ggplot() +
  geom_point(aes(x = max_value, y = delta))

there is a shape here - low max_value households have higher delta (i.e. more reactive/changeable)

hhold_stats %>% 
  filter(min_value != 0)

No, 10,546 still have min of 0 so their range/max will be 1

Try a measure of change being IQR / max

hhold_stats %>% 
  mutate(delta = iqr / max_value) %>% 
  ggplot() +
  geom_point(aes(x = max_value, y = delta))

Could be some groups here, beyond the main cluster - there are hholds with high max values and low delta, suggesting that they remain high energy users regardless (i.e. not much change)

others are low max value and higher delta, indicating they are more ractive/responsive energy consumers

hhold_stats %>% 
  mutate(delta = iqr / max_value) %>% 
  ggplot() +
  geom_point(aes(x = min_value, y = delta))

Plotting v min_value is less informative, many households have low min_value. No real pattern in delta for households with higher min_value - maybe a positive correlation though?

Idea: * split energy data for weekend v weekday before analysing more

Energy use weekend v weekday

Aug 23

We know season has an effect, see above time series

In looking at the seasonal decomposition, we can see weekdays are lower usage than weekends

Split summer v winter, and then split weekday v weekend –> viz & stats

Seasonality

Aug 23

Look at seasonality again

First need to convert data to tsibble:

library(tsibble)
library(slider)
# convert data to tsibble
daily_energy_clean_ts <- as_tsibble(daily_energy_clean, key = lc_lid, index = date)
index_var(daily_energy_ts)
[1] "date"
key_vars(daily_energy_ts)
[1] "lc_lid"

To make rolling average for whole dataset, first find median of all values on a each date (because values are not normally distributed on a single date in December, see above, skewed), then do mean across a 14-day window (set as 7 before, 6 after, only show for complete range).

# make df with median for each day
median_14d_rolling_energy_clean_ts <- daily_energy_clean_ts %>% 
  index_by(date) %>% 
  summarise(median_kwh = median(kwh)) %>%
  # add moving_avg using the mean
  mutate(kwh_moving_avg = slide_dbl(
    .x = median_kwh,
    .f = ~ mean(.),
    .before = 7,
    .after = 6, # 14-day rolling average
    .complete = TRUE
  ))
median_14d_rolling_energy_clean_ts %>% 
  ggplot() + 
  geom_line(aes(x = date, y = median_kwh), colour = "grey") + 
  geom_line(aes(x = date, y = kwh_moving_avg), colour = "red") +
  theme_minimal()

Seasons still look like winter high, summer low. Also looks like energy usage reducing year by year

library(feasts)

Use the median values

median_14d_rolling_energy_clean_ts %>% 
  autoplot(median_kwh) +
  xlab("Date") + ylab("Median daily energy consumption (kWh)")

# look at seasonal pattern
median_14d_rolling_energy_clean_ts %>% 
  gg_season(median_kwh)

Seasonal pattern is consistent year-on-year. Maybe 2014 looks like slightly lower usage? Also note some higher variability in winter 2011 data.

Need to remove last date because values are off? And first date too? Have removed in cleaning step now.

trend: weekdays

Aug 23

Look at subseries:

Can see trend for every weekday - does look like Sat & Sun are higher.

median_14d_rolling_energy_clean_ts %>% 
  gg_subseries(y = median_kwh, period = 7)

Yes, weekends blue lines still look higher than for weekdays

wday v season

Aug 23

Split into winter and summer data only and repeat to see if there is seasonal interaction here

seasons_energy_clean_ts <- daily_energy_clean_ts %>% 
  mutate(weekday = wday(date, label = TRUE), .after = date) %>% 
  mutate(is_weekend = if_else(weekday %in% c("Sat","Sun"), T, F), .after = weekday) %>%
  # note don't use zoo function on tsibble, use yearmonth()
  mutate(yearmonth = yearmonth(date), .after = date) %>% 
  mutate(month = month(date, label = FALSE), .after = date) %>% 
  mutate(quarter = quarter(date, type = "date_first", fiscal_start = 12), .after = date) %>% 
  mutate(yearseason = case_when(
    #quarter == "2011-09-01" ~ "Autumn 2011",
    quarter == "2011-12-01" ~ "Winter 2011",
    quarter == "2012-03-01" ~ "Spring 2012",
    quarter == "2012-06-01" ~ "Summer 2012",
    quarter == "2012-09-01" ~ "Autumn 2012",
    quarter == "2012-12-01" ~ "Winter 2012",
    quarter == "2013-03-01" ~ "Spring 2013",
    quarter == "2013-06-01" ~ "Summer 2013",
    quarter == "2013-09-01" ~ "Autumn 2013",
    quarter == "2013-12-01" ~ "Winter 2013",
    .default = NA_character_
  ), .after = quarter) %>% 
  mutate(yearseason = factor(yearseason, levels = c(#"Autumn 2011",
    "Winter 2011", "Spring 2012", "Summer 2012",
    "Autumn 2012", "Winter 2012", "Spring 2013", 
    "Summer 2013", "Autumn 2013", "Winter 2013")))
# make a summer subset (Summer 2012, 2013)
summer_energy_ts <- seasons_energy_clean_ts %>% 
  filter(str_detect(yearseason, "Summer*"))

winter rolling avg

# add medians & rolling avg for winter only
winter_rolling_ts <- winter_energy_ts %>% 
  index_by(date) %>% 
  summarise(median_kwh = median(kwh)) %>%
  # add moving_avg using the mean
  mutate(kwh_moving_avg = slide_dbl(
    .x = median_kwh,
    .f = ~ mean(.),
    .before = 7,
    .after = 6, # 14-day rolling average
    .complete = TRUE
  )) %>% 
  fill_gaps()
winter_rolling_ts %>% 
  ggplot() + 
  geom_line(aes(x = date, y = median_kwh), colour = "grey") + 
  geom_line(aes(x = date, y = kwh_moving_avg), colour = "red") +
  theme_minimal()

winter_rolling_ts %>% 
  fill_gaps() %>% 
  gg_subseries(y = median_kwh, period = 7)

winter_rolling_wday_ts <- winter_rolling_ts %>% 
  mutate(weekday = wday(date, label = TRUE), .after = date) %>% 
  mutate(is_weekend = if_else(weekday %in% c("Sat","Sun"), T, F), .after = weekday)

winter_rolling_wday_ts
winter_rolling_wday_ts %>% 
  ggplot() +
  geom_boxplot(aes(x = is_weekend, y = median_kwh))

Warning says: Removed 550 rows containing non-finite values

winter_rolling_wday_ts %>% 
  filter(is_weekend) %>% 
  ggplot() +
  geom_histogram(aes(x = median_kwh), bins = 50)

Warning: Removed 157 rows containing non-finite values

Looks approximately normal (some missing values)

winter_rolling_wday_ts %>% 
  filter(!is_weekend) %>% 
  ggplot() +
  geom_histogram(aes(x = median_kwh), bins = 50)

Warning: Removed 393 rows containing non-finite values

Not normal, has 1 row with very low median

winter_rolling_wday_ts %>% 
  arrange(median_kwh)
Warning: Current temporal ordering may yield unexpected results.
ℹ Suggest to sort by ``, `date` first.Warning: Current temporal ordering may yield unexpected results.
ℹ Suggest to sort by ``, `date` first.

The low value is the last date of the trial - looks odd in other graphs, so remove it from the dataset. <– old, dealth with by removing 2014-02-28 in cleaning step

TODO DEAL WITH WARNINGS

todo: DEAL WITH WARNINGS ABOVE Note they sum to 550!

TODO analyse weekend v wday in summer too

TODO

TODO forecasting model

TODO

Daily weather data

Dataset 2, from https://www.kaggle.com/datasets/emmanuelfwerr/london-weather-data.

prep weather data

Aug 23

daily_weather <- read_csv("../3_raw_data/london_weather.csv")
Rows: 15341 Columns: 10── Column specification ──────────────────────────────────────────
Delimiter: ","
dbl (10): date, cloud_cover, sunshine, global_radiation, max_t...
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
skimr::skim(daily_weather)

date is not date value dates cover 1979 to 2020 december, filter to range of energy data

start_date <- min(daily_energy_clean$date)
start_date
[1] "2011-12-01"
end_date <- max(daily_energy_clean$date)
end_date
[1] "2014-02-27"
daily_weather_trim <- daily_weather %>% 
  mutate(date = ymd(date)) %>% 
  filter(date >= start_date & date <= end_date)

skimr::skim(daily_weather_trim)
── Data Summary ────────────────────────
                           Values            
Name                       daily_weather_trim
Number of rows             820               
Number of columns          10                
_______________________                      
Column type frequency:                       
  Date                     1                 
  numeric                  9                 
________________________                     
Group variables            None              

join energy + weather data

Aug 23

# join energy data with weather data
daily_energy_weather <- left_join(x = daily_energy_clean, y = daily_weather_trim, by = join_by(date))
daily_energy_weather

model hholds with weather and time

Aug 23

  • idea: identify types of household - look to see if there are any groups by patterns of consumption (not just overall high/low but variable v fixed over time) - try clustering?

do households respond differently to weather? can we identify households that might need to improve their heating (ie high energy usage on cold days) - both in terms of energy efficiency and home insulation

first, check if mean_temp and kwh correlate (for high/average/low consumer medians) –> load weather data

Remaining ideas:

  • types of weather days - see if there are clusters of temperature, precipitation, cloud cover, sunshine etc –> categories “mild cloudy rainy day” etc

written scripts to prepare data (including joined data), moving to new notebook now to explore weather data and household energy/weather features

LS0tCnRpdGxlOiAiSW5pdGlhbCBkYXRhIGV4cGxvcmF0aW9uIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpUaGlzIG5vdGVib29rIGNvbnRhaW5zIGNvZGUgYW5kIG91dHB1dCBmcm9tIGV4cGxvcmluZyB0aGUgZGF0YSB0byBpbmZvcm0gY2xlYW5pbmcgc3RlcHMgYW5kIGFuYWx5c2lzIHByb2Nlc3MuCgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShsdWJyaWRhdGUpCmBgYAoKIyBMb2FkIGFuZCBpbnNwZWN0IGRhdGEKCiMjIERhaWx5IGVuZXJneSBkYXRhCgpfQXVnIDIxXwoKRGF0YXNldCAxLCBmcm9tIGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vZGF0YXNldHMvZW1tYW51ZWxmd2Vyci9sb25kb24taG9tZXMtZW5lcmd5LWRhdGEuCgpgYGB7cn0KZGFpbHlfZW5lcmd5IDwtIHJlYWRfY3N2KCIuLi8zX3Jhd19kYXRhL2xvbmRvbl9lbmVyZ3kuY3N2IikgJT4lIAogIGphbml0b3I6OmNsZWFuX25hbWVzKCkKYGBgCgpgYGB7cn0Kc2tpbXI6OnNraW0oZGFpbHlfZW5lcmd5KQpgYGAKCjM1MTA0MzMgcm93cyB4IDMgY29scwoKUm93cyBhcmUgb25lIGRhdGUgYW5kIG9uZSBob3VzZWhvbGQgdmFsdWUuIDUsNTY2IGhvdXNlaG9sZHMgb3ZlciA4MjkgZGF5cyAoMjAxMS0xMS0yMyB0byAyMDE0LTAyLTI4KQoKTm8gbWlzc2luZyB2YWx1ZXMgaW4ga2FnZ2xlIGFnZ3JlZ2F0ZWQgZW5lcmd5IGRhdGEgCgpfQXVnIDIzXwoKQlVUIHRoZXJlIGFyZSBtaXNzaW5nIHZhbHVlcyBiZWNhdXNlIG9ubHkgaG91c2Vob2xkcyB3aXRoIHZhbHVlcyBvbiBhIGRhdGUgYXJlIGluY2x1ZGVkLCBub3QgZXZlcnkgaG91c2Vob2xkIGhhcyB2YWx1ZXMgZm9yIGV2ZXJ5IGRhdGUgaW4gcmFuZ2UuIFNlZSBiZWxvdyBzZWN0aW9uIG9uIENMRUFOSU5HIGhvdXNlaG9sZCBkYXRhLgoKYGBge3J9CiMgY2hlY2sgZm9yIGR1cGxpY2F0ZXMgLS0gTk9ORQpkYWlseV9lbmVyZ3kgJT4lIAogIGdyb3VwX2J5KGxjX2xpZCwgZGF0ZSkgJT4lIAogIHN1bW1hcmlzZShjb3VudCA9IG4oKSkgJT4lIAogIGZpbHRlcihjb3VudCA+IDEpCmBgYApObyBkdXBsaWNhdGVzLgoKIyMjIEVuZXJneSBjb25zdW1wdGlvbiBwYXR0ZXJucyBvdmVyIGFsbCBob3VzZWhvbGRzCgojIyMjIGF2ZXJhZ2luZyBkYWlseSBlbmVyZ3kgY29uc3VtcHRpb24gKGFjcm9zcyBhbGwgaG91c2Vob2xkcykKCl9BdWcgMjFfIAoKTm90IHN1cmUgaWYgbWVhbiBpcyBhcHByb3ByaWF0ZSBtZWFzdXJlIGhlcmUsIGxldCdzIGxvb2sgYXQgaGlzdG9ncmFtIG9mIGVuZXJneSBjb25zdW1wdGlvbiB2YWx1ZXMgb2YgZXZlcnkgaG91c2Vob2xkIG9uIGEgc2luZ2xlIGRhdGU6CgpgYGB7cn0KIyBib3hwbG90IG9uIDIwMTItMTItMzEKZGFpbHlfZW5lcmd5ICU+JQogIGZpbHRlcihkYXRlID09ICIyMDEyLTEyLTMxIikgJT4lIAogIGdncGxvdChhZXMoeCA9IGt3aCkpICsgCiAgZ2VvbV9ib3hwbG90KCkKCiMgaGlzdG9ncmFtIG9uIDIwMTItMTItMzEKZGFpbHlfZW5lcmd5ICU+JQogIGZpbHRlcihkYXRlID09ICIyMDEyLTEyLTMxIikgJT4lIAogIGdncGxvdCgpICsgCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHggPSBrd2gpLCBiaW5zID0gMTUwKQpgYGAKCkVuZXJneSBjb25zdW1wdGlvbiBvbiBhbiBpbmRpdmlkdWFsIGRhdGUsIGZyZXF1ZW5jeSBvZiBob3VzZWhvbGRzLCBpcyBub3Qgbm9ybWFsbHkgZGlzdHJpYnV0ZWQgYnV0IHJpZ2h0LXNrZXdlZC4gTWVkaWFuIG1heSBiZSBhIGJldHRlciBhdmVyYWdlIGRhaWx5IG1lYXN1cmUgdGhhbiBtZWFuLgoKIyMjIyBzaW1wbGUgbGluZXMKCl9BdWcgMjFfCgpgYGB7cn0KIyBtZWRpYW4KZGFpbHlfZW5lcmd5ICU+JSAKICBncm91cF9ieShkYXRlKSAlPiUgCiAgbXV0YXRlKG1lZGlhbl9rd2ggPSBtZWRpYW4oa3doKSkgJT4lIAogIGdncGxvdChhZXMoeCA9IGRhdGUsIHkgPSBtZWRpYW5fa3doKSkgKwogIGdlb21fbGluZSgpCmBgYAoKCmBgYHtyfQojIG1lYW4KZGFpbHlfZW5lcmd5ICU+JSAKICBncm91cF9ieShkYXRlKSAlPiUgCiAgbXV0YXRlKG1lYW5fa3doID0gbWVhbihrd2gpKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gZGF0ZSwgeSA9IG1lYW5fa3doKSkgKwogIGdlb21fbGluZSgpCmBgYAoKRGVmaW5pdGVseSBzb21lIHNlYXNvbmFsaXR5IGluIGVuZXJneSBjb25zdW1wdGlvbiwgd2hpY2hldmVyIGF2ZXJhZ2UgdXNlZC4KCiMjIyMgUm9sbGluZyBhdmVyYWdlCgpfQXVndXN0IDIyXyAKClNlZSBjbGFzc25vdGVzIHdrNyBkYXkgMSAobmV3IG5vdGVzIGluIGNvZGVjbGFuX3dvcmsvZmxpcHBlZC4uLiBmb2xkZXIpIC0gdXNlIHBhY2thZ2UgYHNsaWRlcmAKCkZpcnN0IG5lZWQgdG8gY29udmVydCBkYXRhIHRvIHRzaWJibGU6CgpgYGB7cn0KbGlicmFyeSh0c2liYmxlKQpsaWJyYXJ5KHNsaWRlcikKYGBgCgpgYGB7cn0KIyBjb252ZXJ0IGRhdGEgdG8gdHNpYmJsZQpkYWlseV9lbmVyZ3lfdHMgPC0gYXNfdHNpYmJsZShkYWlseV9lbmVyZ3ksIGtleSA9IGxjX2xpZCwgaW5kZXggPSBkYXRlKQpgYGAKCmBgYHtyfQppbmRleF92YXIoZGFpbHlfZW5lcmd5X3RzKQpgYGAKCmBgYHtyfQprZXlfdmFycyhkYWlseV9lbmVyZ3lfdHMpCmBgYAoKVG8gbWFrZSByb2xsaW5nIGF2ZXJhZ2UgZm9yIHdob2xlIGRhdGFzZXQsIGZpcnN0IGZpbmQgbWVkaWFuIG9mIGFsbCB2YWx1ZXMgb24gYSBlYWNoIGRhdGUgKGJlY2F1c2UgdmFsdWVzIGFyZSBub3Qgbm9ybWFsbHkgZGlzdHJpYnV0ZWQgb24gYSBzaW5nbGUgZGF0ZSBpbiBEZWNlbWJlciwgc2VlIGFib3ZlLCBza2V3ZWQpLCB0aGVuIGRvIG1lYW4gYWNyb3NzIGEgMTQtZGF5IHdpbmRvdyAoc2V0IGFzIDcgYmVmb3JlLCA2IGFmdGVyLCBvbmx5IHNob3cgZm9yIGNvbXBsZXRlIHJhbmdlKS4KCmBgYHtyfQojIG1ha2UgZGYgd2l0aCBtZWRpYW4gZm9yIGVhY2ggZGF5Cm1lZGlhbl9hbGxfZGFpbHlfZW5lcmd5X3RzIDwtIGRhaWx5X2VuZXJneV90cyAlPiUgCiAgaW5kZXhfYnkoZGF0ZSkgJT4lIAogIHN1bW1hcmlzZShtZWRpYW5fa3doID0gbWVkaWFuKGt3aCkpCmBgYAoKCmBgYHtyfQojIGNhbGMgcm9sbGluZyAxNC1kYXkgbWVhbgptZWRpYW5fYWxsX2RhaWx5X2VuZXJneV90cyA8LSBtZWRpYW5fYWxsX2RhaWx5X2VuZXJneV90cyAlPiUgCiAgbXV0YXRlKGt3aF9tb3ZpbmdfYXZnID0gc2xpZGVfZGJsKAogICAgLnggPSBtZWRpYW5fa3doLAogICAgLmYgPSB+IG1lYW4oLiksCiAgICAuYmVmb3JlID0gNywKICAgIC5hZnRlciA9IDYsICMgMTQtZGF5IHJvbGxpbmcgYXZlcmFnZQogICAgLmNvbXBsZXRlID0gVFJVRQogICkpCmBgYAoKYGBge3J9Cm1lZGlhbl9hbGxfZGFpbHlfZW5lcmd5X3RzICU+JSAKICBnZ3Bsb3QoKSArIAogIGdlb21fbGluZShhZXMoeCA9IGRhdGUsIHkgPSBtZWRpYW5fa3doKSwgY29sb3VyID0gImdyZXkiKSArIAogIGdlb21fbGluZShhZXMoeCA9IGRhdGUsIHkgPSBrd2hfbW92aW5nX2F2ZyksIGNvbG91ciA9ICJyZWQiKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKTmVlZCB0byBsb29rIGF0IHNlYXNvbmFsIGRlY29tcG9zaXRpb24uCgojIyMjIFNlYXNvbmFsIGRlY29tcG9zaXRpb24KCl9BdWcgMjJfCgpgYGB7cn0KbGlicmFyeShmZWFzdHMpCmBgYAoKVXNlIHRoZSBtZWRpYW4gdmFsdWVzCgpgYGB7cn0KbWVkaWFuX2FsbF9kYWlseV9lbmVyZ3lfdHMgJT4lIAogIGF1dG9wbG90KG1lZGlhbl9rd2gpICsKICB4bGFiKCJEYXRlIikgKyB5bGFiKCJNZWRpYW4gZGFpbHkgZW5lcmd5IGNvbnN1bXB0aW9uIChrV2gpIikKYGBgCgpJbnRlcnByZXQ6CgoqIGxvb2tzIGxpa2UgaGlnaGVyIGVuZXJneSB1c2FnZSBpbiB3aW50ZXIgbW9udGhzIChEZWMtRmViKSwgbG93ZXIgaW4gc3VtbWVyIG1vbnRocyAoSnVuLUF1Zykgd2l0aCBpbmNyZWFzZS9kZWNyZWFzZSBpbiBiZXR3ZWVuCiogdGhpcyBwYXR0ZXJuIHJlcGVhdHMgb3ZlciB0aGUgMi41IHllYXJzIGRhdGEgd2UgaGF2ZSBhdmFpbGFibGUgKDMgd2ludGVycywgMiBzdW1tZXJzKQoqIHRoZXJlIGlzIGZsdWN0dWF0aW9uIHdpdGhpbiB0aGVzZSB0aW1lcyB0b28gLSBhIHdlZWtseSBwYXR0ZXJuPwoKYGBge3J9CiMgbG9vayBhdCBzZWFzb25hbCBwYXR0ZXJuCm1lZGlhbl9hbGxfZGFpbHlfZW5lcmd5X3RzICU+JSAKICBnZ19zZWFzb24obWVkaWFuX2t3aCkKYGBgCgpTZWFzb25hbCBwYXR0ZXJuIGlzIGNvbnNpc3RlbnQgeWVhci1vbi15ZWFyLiBNYXliZSAyMDE0IGxvb2tzIGxpa2Ugc2xpZ2h0bHkgbG93ZXIgdXNhZ2U/IEFsc28gbm90ZSBzb21lIGhpZ2hlciB2YXJpYWJpbGl0eSBpbiB3aW50ZXIgMjAxMSBkYXRhLgpOZWVkIHRvIHJlbW92ZSBsYXN0IGRhdGUgYmVjYXVzZSB2YWx1ZXMgYXJlIG9mZj8gQW5kIGZpcnN0IGRhdGUgdG9vPwoKIyMjIyMgdHJlbmQ6IHdlZWtkYXlzCgpMb29rIGF0IHN1YnNlcmllczoKCkNhbiBzZWUgdHJlbmQgZm9yIGV2ZXJ5IHdlZWtkYXkgLSBkb2VzIGxvb2sgbGlrZSBTYXQgJiBTdW4gYXJlIGhpZ2hlci4KYGBge3J9Cm1lZGlhbl9hbGxfZGFpbHlfZW5lcmd5X3RzICU+JSAKICBnZ19zdWJzZXJpZXMoeSA9IG1lZGlhbl9rd2gsIHBlcmlvZCA9IDcpCmBgYAoKIyMjIyMgcmVzdHJpY3QgdG8gd2ludGVyIG9ubHk/Cgpfb2xkIGNvZGUsIGRpZG4ndCB3b3JrLCBub3Qgc3VyZSB0aGUgUSBpcyByaWdodCBzbyBwYXVzZSB0aGlzIGZvciBub3dfCgpgYGB7cn0KIyBsb29rIG9ubHkgYXQgd2ludGVyIG1vbnRocwpkYWlseV9lbmVyZ3lfdHMgJT4lIAogIG11dGF0ZShxdWFydGVyID0gcXVhcnRlcihkYXRlLCB0eXBlID0gImRhdGVfZmlyc3QiLCBmaXNjYWxfc3RhcnQgPSAxMikpICU+JSAKICBhcnJhbmdlKHF1YXJ0ZXIpICU+JSAKICBpbmRleF9ieShsY19saWQpICU+JSAKICBzdW1tYXJpc2UobWVkaWFuX2t3aCA9IG1lZGlhbihrd2gpKQoKICB1cGRhdGVfdHNpYmJsZShrZXkgPSBsY19saWQsIGluZGV4ID0gcXVhcnRlcikKICAKICBmaWx0ZXJfaW5kZXgobW9udGgoZGF0ZSkgJWluJSBjKDEyLDEsMikpCiAgZ2dfc3Vic2VyaWVzKHkgPSBtZWRpYW5fa3doLCBwZXJpb2QgPSA3KQpgYGAKCgpXaGVuIGxvb2tpbmcgZm9yIHN1YnNlcmllcywgZmlyc3QgY2hhbmdlIGluZGV4IHRvIHNlYXNvbiAodXNpbmcgcXVhcnRlcikgLSB0aGlzIGRvZXNuJ3Qgd29yayBmb3Igc3Vic2VyaWVzLCBuZWVkIGl0IHRvIGJlIGRhdGUgZm9ybWF0CgpgYGB7cn0KcXVhcnRlcmx5X2VuZXJneV90cyA8LSBkYWlseV9lbmVyZ3lfdHMgJT4lIAogICMgbWFrZSBxdWFydGVycyB3aGVyZSBEZWMgaXMgaW4gUTEgb2YgeWVhciAoYWRqdXN0ZWQgYXMgc2Vhc29uIG5vdCByZWFsIHF1YXJ0ZXIpCiAgbXV0YXRlKHF1YXJ0ZXIgPSBxdWFydGVyKGRhdGUsIHR5cGUgPSAiZGF0ZV9maXJzdCIsIGZpc2NhbF9zdGFydCA9IDEyKSkgJT4lIAogIAogIHVwZGF0ZV90c2liYmxlKGtleSA9IE5VTEwsIGluZGV4ID0gcXVhcnRlcikgCgppbmRleF92YXIocXVhcnRlcmx5X2VuZXJneV90cykKCgolPiUKICB1cGRhdGVfdHNpYmJsZShrZXkgPSBsY19saWQsIGluZGV4ID0gcXVhcnRlcikgJT4lIAogICBpbmRleF9ieShxdWFydGVyKSAKICBncm91cF9ieShsY19saWQpICU+JSAKICBzdW1tYXJpc2UobWVkaWFuX2t3aF9wZXJoaCA9IG1lZGlhbihrd2gpKQogIGdnX3N1YnNlcmllcyhtZWRpYW5fa3doKQpgYGAKCgojIyMgRW5lcmd5IGNvbnN1bXB0aW9uIHBhdHRlcm5zIHdpdGhpbiBob3VzZWhvbGRzCgojIyMjIEluZGl2aWRhbCBob3VzZWhvbGRzCgpfQXVnIDIxXwoKTGV0J3MgbG9vayBhdCBhIGZldyBpbmRpdmlkdWFsIGhvdXNlaG9sZHM6CgpgYGB7cn0KZGFpbHlfZW5lcmd5ICU+JSAKICBkaXN0aW5jdChsY19saWQpCmBgYAoKYGBge3J9CmRhaWx5X2VuZXJneSAlPiUgCiAgZmlsdGVyKGxjX2xpZCAlaW4lIGMoIk1BQzAwMDAwMiIsICJNQUMwMDAwNTgiLCAiTUFDMDAwMzU5IiwgIk1BQzAwMDcxNCIsICJNQUMwMDA5MzMiLCAiTUFDMDAwOTk3IikpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBkYXRlLCB5ID0ga3doLCBjb2xvdXIgPSBsY19saWQpKSArCiAgZ2VvbV9saW5lKCkgKwogIGZhY2V0X3dyYXAofmxjX2xpZCkKYGBgCgpDYW4gYWxyZWFkeSBzZWUgaG91c2Vob2xkcyBhcmUgYmVoYXZpbmcgZGlmZmVyZW50bHksIHNvbWUgaGlnaCBjb25zdW1lcnMsIG90aGVycyBsZXNzIHNvOyBzb21lIGNoYW5nZSBvdmVyIHRoZSB5ZWFyLCBzb21lIGxlc3Mgc28uCgpNaWdodCB3YW50IHRvIHVzZSBhIHJvbGxpbmcgYXZlcmFnZSBoZXJlLCBlLmcuIDctZGF5LCB0byBnaXZlIGEgc21vb3RoZXIgbGluZSAoYXMgYWJvdmUhKQoKTWlnaHQgYWxzbyB3YW50IHRvIGNvbnNpZGVyIGlmIHRoZXJlIGFyZSAidHlwZXMiIG9mIGhvdXNlaG9sZCB0byBzcGxpdCB0aGUgZGF0YSBieSwgZS5nLiBhY2NvcmRpbmcgdG8gaG93IHZhcmlhYmxlIHRoZXkgYXJlIGluIGEgeWVhciwgdGhlaXIgbGV2ZWwgb2YgY29uc3VtcHRpb24gKGhpZ2gvbG93KSAtIHNlZSBtb3JlIGJlbG93LgoKIyMjIyB0eXBlcyBvZiBob3VzZWhvbGQgKGJ5IGNvbnN1bXB0aW9uIHBhdHRlcm5zKQoKX0F1ZyAyMV8KCmxvb2sgdG8gc2VlIGlmIHRoZXJlIGFyZSBvYnZpb3VzIGhpZ2gvbWVkaXVtL2xvdyBjb25zdW1lciBncm91cHMKCmBgYHtyfQpob3VzZWhvbGRfYWxsdGltZV9jb25zdW1wdGlvbiA8LSBkYWlseV9lbmVyZ3kgJT4lIAogIGdyb3VwX2J5KGxjX2xpZCkgJT4lIAogIHN1bW1hcmlzZShhbGx0aW1lX21lZGlhbl9rd2ggPSBtZWRpYW4oa3doKSwKICAgICAgICAgICAgYWxsdGltZV9tZWFuX2t3aCA9IG1lYW4oa3doKSkKYGBgCgpgYGB7cn0KaG91c2Vob2xkX2FsbHRpbWVfY29uc3VtcHRpb24KYGBgCgpgYGB7cn0KaG91c2Vob2xkX2FsbHRpbWVfY29uc3VtcHRpb24gJT4lIAogIGdncGxvdCgpICsKICBnZW9tX2hpc3RvZ3JhbShhZXMoeCA9IGFsbHRpbWVfbWVkaWFuX2t3aCksIGJpbnMgPSAyMDApCmBgYAoKTG9vayBhdCBqYW51YXJ5IG9yIGp1bHkgZGF0ZXMgb25seSwgd2hlbiB0aGVyZSBtYXkgYmUgbW9yZSB2YXJpYXRpb24gaW4gdXNhZ2UgKGNvbGQgYW5kIGhvdCB3ZWF0aGVyKQoKYGBge3J9CmhvdXNlaG9sZF9qYW5fanVsX2NvbnN1bXB0aW9uIDwtIGRhaWx5X2VuZXJneSAlPiUgCiAgbXV0YXRlKG1vbnRoID0gbW9udGgoZGF0ZSwgbGFiZWwgPSBUUlVFLCBhYmJyID0gRkFMU0UpKSAlPiUgCiAgZmlsdGVyKG1vbnRoICVpbiUgYygiSmFudWFyeSIsIkp1bHkiKSkgJT4lIAogIGdyb3VwX2J5KGxjX2xpZCwgbW9udGgpICU+JSAKICBzdW1tYXJpc2UobWVkaWFuX2t3aCA9IG1lZGlhbihrd2gpLAogICAgICAgICAgICBtZWFuX2t3aCA9IG1lYW4oa3doKSkKYGBgCgpgYGB7cn0KIyBqYW51YXJ5IG1lZGlhbnMKaG91c2Vob2xkX2phbl9qdWxfY29uc3VtcHRpb24gJT4lIAogIGZpbHRlcihtb250aCA9PSAiSmFudWFyeSIpICU+JSAKICBnZ3Bsb3QoKSArCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHggPSBtZWRpYW5fa3doKSwgYmlucyA9IDIwMCkKCiMganVseSBtZWRpYW5zCmhvdXNlaG9sZF9qYW5fanVsX2NvbnN1bXB0aW9uICU+JSAKICBmaWx0ZXIobW9udGggPT0gIkp1bHkiKSAlPiUgCiAgZ2dwbG90KCkgKwogIGdlb21faGlzdG9ncmFtKGFlcyh4ID0gbWVkaWFuX2t3aCksIGJpbnMgPSAyMDApCmBgYAoKTW9zdCBob3VzZWhvbGRzIGhhdmUgbWVkaWFuIGRhaWx5IGNvbnN1bXB0aW9uIGluIHJhbmdlIDAtMzBrV2ggaW4gSmFudWFyeSBhbmQgMC0yMCBrV2ggaW4gSnVseSwgaW5kaWNhdGluZyBlbmVyZ3kgdXNhZ2UgaXMgaGlnaGVzdCBpbiB3aW50ZXIKCmBgYHtyfQpob3VzZWhvbGRfamFuX2p1bF9jb25zdW1wdGlvbiAlPiUgCiAgZ2dwbG90KCkgKwogIGdlb21fYm94cGxvdChhZXMoeCA9IG1vbnRoLCB5ID0gbWVkaWFuX2t3aCkpCmBgYAoKTWF5YmUgZW5lcmd5IGNvbnN1bXB0aW9uIGluIEphbnVhcnkgaXMgc2xpZ2h0bHkgbW9yZSB0aGFuIGluIEp1bHkuIENvdWxkIHRlc3QgdGhpcy4gTG9uZyB0YWlsIG9mIGhpZ2ggZW5lcmd5IGNvbnN1bWVycy4KCk5vIG9idmlvdXMgZ3JvdXBzIHRvIHNwbGl0IGludG8sIGJ1dCBjb3VsZCBsYWJlbCBob3VzZWhvbGRzIGFib3ZlIFEzIGFzICJoaWdoIGNvbnN1bWVycyIsIGJlbG93IFExIGFzICJsb3cgY29uc3VtZXJzIiwgYW5kIHJlc3QgYXMgIm1lZGl1bSBjb25zdW1lcnMiLgoKYGBge3J9CmFsbF9rd2hfc3RhdHMgPC0gZGFpbHlfZW5lcmd5ICU+JSAKICBzZWxlY3Qoa3doKSAlPiUgCiAgc2tpbXI6OnNraW0oKQoKYWxsX2t3aF9zdGF0cyAlPiUgCiAgY29sbmFtZXMoKQoKa3doX3ExIDwtIGFsbF9rd2hfc3RhdHMgJT4lIAogIHNlbGVjdChudW1lcmljLnAyNSkgJT4lIAogIHB1bGwoKQoKa3doX3EzIDwtIGFsbF9rd2hfc3RhdHMgJT4lIAogIHNlbGVjdChudW1lcmljLnA3NSkgJT4lIAogIHB1bGwoKQoKa3doX3ExCmt3aF9xMwpgYGAKTm90ZSB0aGVzZSBhcmUgY2FsY3VsYXRlZCBmcm9tIGFsbCBkYXRlcywgbm90IGFkanVzdGVkIGZvciBqYW4vanVseS9zZWFzb25hbGl0eSBhbmQgbm90ZSB0aGUgZGF0ZSByYW5nZSBjb3ZlcnMgbW9yZSB3aW50ZXJzICgzKSB0aGFuIHN1bW1lcnMgKDIpLgoKYGBge3J9CiMgYWRkIGhvdXNlaG9sZCB0eXBlLCBjYW4gYmUgdXNlZCBhcyBsb29rdXAga2V5LXZhbHVlIChqb2luIHdpdGggZnVsbCBkYXRhKQpob3VzZWhvbGRfYWxsdGltZV9jb25zdW1wdGlvbiA8LSBob3VzZWhvbGRfYWxsdGltZV9jb25zdW1wdGlvbiAlPiUgCiAgbXV0YXRlKGhvdXNlaG9sZF9jb25zdW1wdGlvbl9sZXZlbCA9IGNhc2Vfd2hlbigKICAgIGFsbHRpbWVfbWVkaWFuX2t3aCA+PSBrd2hfcTMgfiAiaGlnaCIsCiAgICBhbGx0aW1lX21lZGlhbl9rd2ggPiBrd2hfcTEgfiAiYXZlcmFnZSIsCiAgICBhbGx0aW1lX21lZGlhbl9rd2ggPD0ga3doX3ExIH4gImxvdyIsCiAgICAuZGVmYXVsdCA9IE5BX2NoYXJhY3Rlcl8KICApLAogIGhvdXNlaG9sZF9jb25zdW1wdGlvbl9sZXZlbCA9IGZhY3Rvcihob3VzZWhvbGRfY29uc3VtcHRpb25fbGV2ZWwsIGxldmVscyA9IGMoImxvdyIsICJhdmVyYWdlIiwgImhpZ2giKSkpCmBgYAoKYGBge3J9CmhvdXNlaG9sZF9hbGx0aW1lX2NvbnN1bXB0aW9uICU+JSAKICBnZ3Bsb3QoKSArCiAgZ2VvbV9ib3hwbG90KGFlcyh4ID0gaG91c2Vob2xkX2NvbnN1bXB0aW9uX2xldmVsLCB5ID0gYWxsdGltZV9tZWRpYW5fa3doKSkKYGBgCgpgYGB7cn0KaG91c2Vob2xkX2FsbHRpbWVfY29uc3VtcHRpb24gJT4lIAogIHN1bW1hcmlzZShjb3VudCA9IG4oKSwgLmJ5ID0gaG91c2Vob2xkX2NvbnN1bXB0aW9uX2xldmVsKQpgYGAKClNlZW1zIGxpa2UgYSBwb3RlbnRpYWxseSB1c2VmdWwgc3BsaXQgb2YgZGF0YQoKYGBge3J9CmRhaWx5X2VuZXJneV9wcm9jZXNzZWQgPC0gbGVmdF9qb2luKGRhaWx5X2VuZXJneSwgaG91c2Vob2xkX2FsbHRpbWVfY29uc3VtcHRpb24sIGJ5ID0gImxjX2xpZCIpCgpkYWlseV9lbmVyZ3lfcHJvY2Vzc2VkCmBgYAoKTG9vayBhdCBtZWRpYW4gZW5lcmd5IG92ZXIgdGltZSBieSB0eXBlOgoKYGBge3J9CmRhaWx5X2VuZXJneV9wcm9jZXNzZWRfbWVkaWFucyA8LSBkYWlseV9lbmVyZ3lfcHJvY2Vzc2VkICU+JSAKICBncm91cF9ieShkYXRlLCBob3VzZWhvbGRfY29uc3VtcHRpb25fbGV2ZWwpICU+JSAKICBzdW1tYXJpc2UobWVkaWFuX2t3aCA9IG1lZGlhbihrd2gpKSAKCmRhaWx5X2VuZXJneV9wcm9jZXNzZWRfbWVkaWFucyAlPiUgCiAgYXJyYW5nZShkYXRlKQoKZGFpbHlfZW5lcmd5X3Byb2Nlc3NlZF9tZWRpYW5zICU+JSAKICBnZ3Bsb3QoKSArCiAgZ2VvbV9saW5lKGFlcyh4ID0gZGF0ZSwgeSA9IG1lZGlhbl9rd2gsIGNvbG91ciA9IGhvdXNlaG9sZF9jb25zdW1wdGlvbl9sZXZlbCkpCmBgYAoKVGhlcmUgbWF5IGJlIGEgd2F5IHRvIGdyb3VwIGhvdXNlaG9sZHMgYnkgY29uc3VtcHRpb24gaW4gYW4gdW5zdXBlcnZpc2VkIHdheSwgaS5lLiBieSBjbHVzdGVyaW5nIGluc3RlYWQuLiB0YmMKCiMjIyMgZmluZGluZyBleGFtcGxlIGhvdXNlaG9sZHMKCl9BdWcgMjNfCgojIyMjIyBDTEVBTjogbWlzc2luZyBkYXRhIGFuZCB6ZXJvIHZhbHVlcwoKYGBge3J9CiMgZmluZCBob3VzZWhvbGRzIHdpdGggdGhlIDEwIGxvd2VzdCBrd2ggc2NvcmVzCmRhaWx5X2VuZXJneV9wcm9jZXNzZWQgJT4lIAogIGFycmFuZ2Uoa3doKSAlPiUgCiAgaGVhZCgxMCkKYGBgCgpBcnJhbmdpbmcgYnkga3doIGxvdyB0byBoaWdoIHNob3dzIGxvdHMgb2YgemVyb3MgLS0gaW52ZXN0aWdhdGU6CmBgYHtyfQpoaG9sZHNfemVyb192YWx1ZXMgPC0gZGFpbHlfZW5lcmd5X3Byb2Nlc3NlZCAlPiUgCiAgZmlsdGVyKGt3aCA9PSAwKSAlPiUgICMgMTUsMTY4IHJvd3MKICBzdW1tYXJpc2UoY291bnQgPSBuKCksIC5ieSA9IGxjX2xpZCkgJT4lIAogIGFycmFuZ2UoZGVzYyhjb3VudCkpCgpoaG9sZHNfemVyb192YWx1ZXMKYGBgCgozMDggaG91c2Vob2xkcyB3aXRoIDAgdmFsdWVzLiAKCmBgYHtyfQpoaG9sZHNfemVyb192YWx1ZXMgJT4lIAogIGdncGxvdCgpICsKICBnZW9tX2NvbChhZXMoeCA9IGxjX2xpZCwgeSA9IGNvdW50KSkKYGBgCgpgYGB7cn0KaGhvbGRzX3dpdGhfemVyb3MgPC0gaGhvbGRzX3plcm9fdmFsdWVzICU+JSAKICBwdWxsKGxjX2xpZCkKCmRhaWx5X2VuZXJneV9wcm9jZXNzZWQgJT4lIAogIGZpbHRlcihsY19saWQgJWluJSBoaG9sZHNfd2l0aF96ZXJvcykgJT4lIAogIG11dGF0ZSh6ZXJvX2t3aF92YWx1ZSA9IGlmX2Vsc2Uoa3doID09IDAsIFQsIEYpLCAuYmVmb3JlID0ga3doKSAlPiUgCiAgZ2dwbG90KCkgKwogIGdlb21fbGluZShhZXMoeCA9IGRhdGUsIHkgPSB6ZXJvX2t3aF92YWx1ZSwgZ3JvdXAgPSBsY19saWQpLCBhbHBoYSA9IDAuMiwgY29sb3VyID0gImdyZXkiKQpgYGAKCmBgYHtyfQpkYWlseV9lbmVyZ3lfcHJvY2Vzc2VkICU+JSAKICBmaWx0ZXIobGNfbGlkICVpbiUgaGhvbGRzX3dpdGhfemVyb3MpICU+JSAKICBtdXRhdGUoemVyb19rd2hfdmFsdWUgPSBpZl9lbHNlKGt3aCA9PSAwLCBULCBGKSwgLmJlZm9yZSA9IGt3aCkgJT4lIAogIGdncGxvdCgpICsKICBnZW9tX3BvaW50KGFlcyh4ID0gZGF0ZSwgeSA9IHplcm9fa3doX3ZhbHVlKSwgY29sb3VyID0gImdyZXkiLCBhbHBoYSA9IDAuMSkKYGBgCk1heWJlIHNvbWUgaG91c2Vob2xkcyB3ZXJlbid0IGNvbGxlY3RpbmcgZGF0YSAvIGRpZG4ndCBqb2luIHVudGlsIGxhdGVyIGluIHRoZSBwZXJpb2QuIAoKYGBge3J9CmRhaWx5X2VuZXJneV9wcm9jZXNzZWQgJT4lIAogIGZpbHRlcihkYXRlID4gIjIwMTMtMDEtMDEiICYga3doID09IDApICMgc3RpbGwgMTAsNTAwIHplcm9lcwpgYGAKCmBgYHtyfQpkYWlseV9lbmVyZ3lfcHJvY2Vzc2VkICU+JSAKICBkaXN0aW5jdChkYXRlKQpgYGAKCjgyOSBkYXlzCgpOb3RlIG5vIGhvdXNlaG9sZHMgYXJlIGFsbCB6ZXJvIHZhbHVlcyB0aG91Z2gsIGhpZ2hlc3QgbnVtYmVyIG9mIHplcm9lcyBpcyA3ODkgZGF5cy4KCkNvdWxkIGRyb3AgaG91c2Vob2xkcyB0aGF0IGhhdmUgYSB6ZXJvIHZhbHVlIGZvciBtb3JlIHRoYW4gNDAlIGRheXMgaW4gdGhlIHRpbWUgcmFuZ2UsIHNpbmNlIHdlIGFyZSBpbnRlcmVzdGVkIGluIGVzdGltYXRpbmcvdW5kZXJzdGFuZGluZyBlbmVyZ3kgdXNhZ2UsIGFuZCB6ZXJvIGt3aCBpbmRpY2F0ZXMgc29tZXRoaW5nIGVsc2UgaXMgZ29pbmcgb24gaGVyZS4KCmBgYHtyfQowLjQqODI5CmBgYAoKU286IFJlbW92ZSB0aGUgMTAgaGhvbGRzIHdpdGggPj0gMzMxIHplcm8gdmFsdWVzIChpLmUgNDAlIG9yIG1vcmUgZGF5cyB3aXRoIDAga3doKToKCmBgYHtyfQptaXNzaW5nX2RhdGFfaGhvbGRzIDwtIGRhaWx5X2VuZXJneV9wcm9jZXNzZWQgJT4lIAogIGZpbHRlcihrd2ggPT0gMCkgJT4lICAjIDE1LDE2OCByb3dzCiAgc3VtbWFyaXNlKGNvdW50ID0gbigpLCAuYnkgPSBsY19saWQpICU+JSAKICBmaWx0ZXIoY291bnQgPj0gMzMxKSAlPiUgCiAgcHVsbChsY19saWQpCgptaXNzaW5nX2RhdGFfaGhvbGRzCmBgYAoKQ291bGQgaW1wdXRlIHdpdGggZWFjaCBob3VzZWhvbGQncyBtZWRpYW4ga3doIHZhbHVlIGZvciB0aGF0IG1vbnRoIGFuZCBpZiB3ZWVrZGF5IG9yIHdlZWtlbmQuIE9SIGNvdWxkIGRyb3AgdGhlc2UgaG91c2Vob2xkcyAocmVtZW1iZXIgd2UgaGF2ZSA1NTAwISkKCklkZWFsbHksIHRyeSBib3RoIGFwcHJvYWNoZXMgYW5kIHNlZSB3aGljaCBnaXZlcyBiZXR0ZXIgcmVzdWx0CgpgYGB7cn0KIyBhZGQgZGF0ZSBmYWN0b3JzCmRhaWx5X2VuZXJneV90cmltIDwtIGRhaWx5X2VuZXJneV9wcm9jZXNzZWQgJT4lIAogICMgcmVtb3ZlIGhvdXNlaG9sZHMgd2l0aCA0MCUrIGRhdGVzIHdpdGggemVybyBrd2ggdmFsdWVzCiAgZmlsdGVyKCFsY19saWQgJWluJSBtaXNzaW5nX2RhdGFfaGhvbGRzKSAlPiUgCiAgbXV0YXRlKHdlZWtkYXkgPSB3ZGF5KGRhdGUsIGxhYmVsID0gVFJVRSksIC5hZnRlciA9IGRhdGUpICU+JSAKICBtdXRhdGUoaXNfd2Vla2VuZCA9IGlmX2Vsc2Uod2Vla2RheSAlaW4lIGMoIlNhdCIsIlN1biIpLCBULCBGKSwgLmFmdGVyID0gd2Vla2RheSkgJT4lCiAgbXV0YXRlKHllYXJtb250aCA9IHpvbzo6YXMueWVhcm1vbihkYXRlKSwgLmFmdGVyID0gZGF0ZSkgJT4lIAogIG11dGF0ZShtb250aCA9IG1vbnRoKGRhdGUsIGxhYmVsID0gRkFMU0UpLCAuYWZ0ZXIgPSBkYXRlKSAlPiUgCiAgbXV0YXRlKHF1YXJ0ZXIgPSBxdWFydGVyKGRhdGUsIHR5cGUgPSAiZGF0ZV9maXJzdCIsIGZpc2NhbF9zdGFydCA9IDEyKSwgLmFmdGVyID0gZGF0ZSkgJT4lIAogIG11dGF0ZSh5ZWFyc2Vhc29uID0gY2FzZV93aGVuKAogICAgcXVhcnRlciA9PSAiMjAxMS0wOS0wMSIgfiAiQXV0dW1uIDIwMTEiLAogICAgcXVhcnRlciA9PSAiMjAxMS0xMi0wMSIgfiAiV2ludGVyIDIwMTEiLAogICAgcXVhcnRlciA9PSAiMjAxMi0wMy0wMSIgfiAiU3ByaW5nIDIwMTIiLAogICAgcXVhcnRlciA9PSAiMjAxMi0wNi0wMSIgfiAiU3VtbWVyIDIwMTIiLAogICAgcXVhcnRlciA9PSAiMjAxMi0wOS0wMSIgfiAiQXV0dW1uIDIwMTIiLAogICAgcXVhcnRlciA9PSAiMjAxMi0xMi0wMSIgfiAiV2ludGVyIDIwMTIiLAogICAgcXVhcnRlciA9PSAiMjAxMy0wMy0wMSIgfiAiU3ByaW5nIDIwMTMiLAogICAgcXVhcnRlciA9PSAiMjAxMy0wNi0wMSIgfiAiU3VtbWVyIDIwMTMiLAogICAgcXVhcnRlciA9PSAiMjAxMy0wOS0wMSIgfiAiQXV0dW1uIDIwMTMiLAogICAgcXVhcnRlciA9PSAiMjAxMy0xMi0wMSIgfiAiV2ludGVyIDIwMTMiLAogICAgLmRlZmF1bHQgPSBOQV9jaGFyYWN0ZXJfCiAgKSwgLmFmdGVyID0gcXVhcnRlcikgJT4lIAogIG11dGF0ZSh5ZWFyc2Vhc29uID0gZmFjdG9yKHllYXJzZWFzb24sIGxldmVscyA9IGMoCiAgICAiQXV0dW1uIDIwMTEiLCAiV2ludGVyIDIwMTEiLCAiU3ByaW5nIDIwMTIiLCAiU3VtbWVyIDIwMTIiLAogICAgIkF1dHVtbiAyMDEyIiwgIldpbnRlciAyMDEyIiwgIlNwcmluZyAyMDEzIiwgIlN1bW1lciAyMDEzIiwKICAgICJBdXR1bW4gMjAxMyIsICJXaW50ZXIgMjAxMyIpKSkKYGBgCgpTbywgZGFpbHlfZW5lcmd5X3RyaW0gaGFzIDEwIGZld2VyIGhvdXNlaG9sZHMgKDUsNTU3PykKCkNoZWNrIHdlZWtkYXlzOiAKKiAyOCBmZWIgMjAxNCB3YXMgYSBmcmlkYXkgLSB5ZXMKKiAxMiBvY3QgMjAxMiB3YXMgYWxzbyBhIGZyaWRheSAtIHllcwoqIGNoZWNrIHllYXJzZWFzb24gYXJyYW5nZXMgaW4gY29ycmVjdCBvcmRlciAtIHllcwoKYGBge3J9CmRhaWx5X2VuZXJneV90cmltICU+JSAKICBmaWx0ZXIoa3doID09IDApICMgbm93IDksNjMzIHplcm8gdmFsdWVzCmBgYAoKCmBgYHtyfQojIGltcHV0ZSBtaXNzaW5nIGt3aCB2YWx1ZXMgd2l0aCBtZWRpYW4gdmFsdWUgZm9yIHRoYXQgaGhvbGQgdGhhdCB5ZWFyc2Vhc29uIGFuZCB3ZGF5IHR5cGUKZGFpbHlfZW5lcmd5X2ltcHV0ZWQgPC0gZGFpbHlfZW5lcmd5X3RyaW0gJT4lIAogIG11dGF0ZSh6ZXJvX2t3aF92YWx1ZSA9IGlmX2Vsc2Uoa3doID09IDAsIFQsIEYpLCAuYmVmb3JlID0ga3doKSAlPiUgCiAgZ3JvdXBfYnkobGNfbGlkLCB5ZWFyc2Vhc29uLCBpc193ZWVrZW5kKSAlPiUgCiAgbXV0YXRlKGhoX2F2Z19zZWFzb25fd2RheSA9IG1lYW4oa3doKSwgLmFmdGVyID0ga3doKSAlPiUgCiAgdW5ncm91cCgpICU+JSAKICBtdXRhdGUoa3doX2ltcHV0ZWQgPSBpZl9lbHNlKGt3aCA9PSAwLCBoaF9hdmdfc2Vhc29uX3dkYXksIGt3aCksIC5iZWZvcmUgPSBrd2gpCmBgYAoKYGBgIHtyfQpkYWlseV9lbmVyZ3lfaW1wdXRlZCAgJT4lIAogIGZpbHRlcih6ZXJvX2t3aF92YWx1ZSA9PSBUKSAjIDksNjMzIHZhbHVlcyBuZWVkZWQgaW1wdXRpbmcgCgpkYWlseV9lbmVyZ3lfaW1wdXRlZCAgJT4lIAogIGZpbHRlcih6ZXJvX2t3aF92YWx1ZSA9PSBUICYga3doX2ltcHV0ZWQgPT0gMCkKIyB1c2luZyBtZWRpYW46IDQsNjQ4IHZhbHVlcyByZW1haW4KIyB1c2luZyBtZWFuOiA4MDcgemVybyB2YWx1ZXMgcmVtYWluCgoKIyMgYmVmb3JlIHJlbW92aW5nIHRoZSAxMCBoaG9sZHMgd2l0aCA0MCUrIHplcm8gZGF5cwojIDE1LDE2OCB2YWx1ZXMgd2VyZSB6ZXJvCiMgdXNpbmcgbWVkaWFuOiAxMCwwMzcgemVybyB2YWx1ZXMgcmVtYWluIGFmdGVyIHRyeWluZyB0byBpbXB1dGUgd2l0aCBtZWRpYW4gZm9yIHdkYXkgJiBzZWFzb24gcGVyIGhob2xkCiMgdXNpbmcgbWVhbjogMywxOTUgemVybyB2YWx1ZXMgcmVtYWluIC0gdXNlIG1lYW4hCmBgYAoKU28gZGFpbHlfZW5lcmd5X2ltcHV0ZWQgY2xlYW5pbmcgc3RlcHM6CiogcmVtb3ZlIDEwIGhvdXNlaG9sZHMgd2l0aCA0MCUrIGRheXMgd2l0aCB6ZXJvIGtXaCAtLSB0aGVzZSBhcmUgdW51c3VhbCBjYXNlcywgbWF5YmUgaW5kaWNhdGUgZ2V0dGluZyBlbmVyZ3kgZnJvbSBlbHNld2hlcmUsIG9yIHByb3BlcnRpZXMgbm90IGJlaW5nIGxpdmVkIGluIG11Y2gsIG9yIHNvbWV0aGluZyBub3Qgd29ya2luZyB3aXRoIHRoZWlyIHNtYXJ0IG1ldGVyCiogaW1wdXRlIHJlbWFpbmluZyB6ZXJvZXMgdXNpbmcgdGhlIG1lYW4ga3doIGZvciB0aGF0IGhvdXNlaG9sZCBpbiB0aGF0IHNlYXNvbiBhbmQgYWNjb3JkaW5nIHRvIHdlZWtkYXkvd2Vla2VuZCB0eXBlCiogODA3IHplcm8gdmFsdWVzIHJlbWFpbiBpbiB0aGUgZGF0YSwgbGVhdmUgdGhlbSBpbiwgbG93ICUgb2YgdGhlIHRvdGFsIDMsNTAzLDg2NyBvYnNlcnZhdGlvbnMgKDAuMDIlKQoKYGBge3J9CjEwMCo4MDcvbnJvdyhkYWlseV9lbmVyZ3lfaW1wdXRlZCkKYGBgCgp3aGF0IGRvZXMgdGhlIGRhdGEgbG9vayBsaWtlIG5vdyBmb3IgYSBob3VzZWhvbGQgd2l0aCBzb21lIHplcm9lcyBpbXB1dGVkPwoKIk1BQzAwMjA1MCIgaGFkIDMxNiB6ZXJvZXMsIHNvIG5vdCByZW1vdmVkLgoiTUFDMDAyMDcyIiBoYWQgMzAzLgoKYGBge3J9CiMgY2hlY2sgaG91c2Vob2xkcyB3aXRoIGxvdHMgb2YgemVybyB2YWx1ZXMgTUFDMDAwMTk3LCBNQUMwMDQwNjcsIE1BQzAwMDAzNwpkYWlseV9lbmVyZ3lfaW1wdXRlZCAlPiUgCiAgZmlsdGVyKGxjX2xpZCAlaW4lIGMoIk1BQzAwMjA1MCIsICJNQUMwMDIwNzIiKSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gZGF0ZSwgZ3JvdXAgPSBsY19saWQpKSArCiAgZ2VvbV9saW5lKGFlcyh5ID0ga3doKSwgY29sb3VyID0gImJsdWUiLCBsaW5ldHlwZSA9IDEpICsKICBnZW9tX2xpbmUoYWVzKHkgPSBrd2hfaW1wdXRlZCksIGNvbG91ciA9ICJyZWQiLCBsaW5ldHlwZSA9IDEpICsKICBmYWNldF93cmFwKH4gbGNfbGlkLCBuY29sID0gMSkKYGBgCgpBY3R1YWxseSwgdGhlcmUgYXJlIHdob2xlIHBlcmlvZHMgb2YgemVyb2VzIC0tIHRoZXNlIHNob3VsZCBhbHNvIGJlIHJlbW92ZWQKCmBgYHtyfQpoaG9sZHNfemVyb192YWx1ZXMgJT4lIAogIGZpbHRlcighbGNfbGlkICVpbiUgbWlzc2luZ19kYXRhX2hob2xkcykgIyBmaWx0ZXIgZm9yIGhob2xkcyBub3QgYWxyZWFkeSByZW1vdmVkCmBgYAoKZS5nLiBNQUMwMDM3MDcsIE1BQzAwMjc3MSBoYXZlIDEwMCB6ZXJvZXM7IE1BQzAwMTY1NSBoYXMgNzggemVyb2VzOyBNQUMwMDQ3NTYgaGFzIDQxCgpgYGB7cn0KZGFpbHlfZW5lcmd5X2ltcHV0ZWQgJT4lIAogIGZpbHRlcihsY19saWQgJWluJSBjKCJNQUMwMDM3MDciLCAiTUFDMDAyNzcxIiwgIk1BQzAwMTY1NSIsICJNQUMwMDQ3NTYiKSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gZGF0ZSwgZ3JvdXAgPSBsY19saWQpKSArCiAgZ2VvbV9saW5lKGFlcyh5ID0ga3doKSwgY29sb3VyID0gImJsdWUiLCBsaW5ldHlwZSA9IDEpICsKICBnZW9tX2xpbmUoYWVzKHkgPSBrd2hfaW1wdXRlZCksIGNvbG91ciA9ICJyZWQiLCBsaW5ldHlwZSA9IDEpICsKICBmYWNldF93cmFwKH4gbGNfbGlkLCBuY29sID0gMSkKYGBgCgoqIE1BQzAwMTY1NSBoYXMgc21hbGwgcGVyaW9kcyBvZiB6ZXJvZXMgZXZlcnkgbm93IGFuZCB0aGVuIC0gcG9zc2libGUgdGhlc2UgYXJlIGhvbGlkYXkgLyBub24tcmVzaWRlbnRpYWwgcGVyaW9kcyB3aGVuIHRoZXkgdHVybiBldmVyeXRoaW5nIG9mZiwgbG9va3MgbGlrZSBhIHJlYWwgc2NlbmFyaW8sIGltcHV0YXRpb25zIGRvbid0IGxvb2sgYmFkIGZvciB0aGUgZGF0YQoqIE1BQzAwMjc3MSBoYXMgYSB3aG9sZSBjaHVuayBvZiB6ZXJvcywgYW5kIGxvdyB2YWx1ZXMgdGhyb3VnaG91dAoqIE1BQzAwMzcwNyAtLSBjaHVua3Mgb2YgemVybyB2YWx1ZXMsIGRvZXMgbm90IGxvb2sgcmVsaWFibGUKKiBNQUMwMDQ3NTYgLS0gY2xlYXJseSBtaXNzaW5nIGxvdHMgb2YgZGF0YSBhZnRlciBhIHBvaW50LCBzaG91bGQgaGF2ZSBtb3JlIHRoYW4gdGhhdCBudW1iZXIgb2YgMHMuLi4KCiMjIyMjIHJlc3RhcnQgQ0xFQU5JTkcKCkNoZWNrIHRoYXQgZWFjaCBob3VzZWhvbGQgaGFzIGZ1bGwgODI5IGRheXMKCmBgYHtyfQpkYWlseV9lbmVyZ3lfcHJvY2Vzc2VkICU+JSAKICBncm91cF9ieShsY19saWQpICU+JSAKICBzdW1tYXJpc2UoY291bnQgPSBuKCkpICU+JSAKICBmaWx0ZXIoY291bnQgPCA4MjkpCmBgYAoKNSw1NTUgb2YgdGhlIDUsNTY2IGhvdXNlaG9sZHMgZG8gbm90IGhhdmUgY29tcGxldGUgZGF0YSBmb3IgdGhlIHdob2xlIHRpbWUgcGVyaW9kISBpLmUuIG9ubHkgMTEgaG91c2Vob2xkcyBoYXZlIGRhdGEgZm9yIHRoZSB3aG9sZSB0aW1lLgoKV2hhdCBpcyB0aGUgdGhyZXNob2xkIGF0IHdoaWNoIEkgZG9uJ3QgaW5jbHVkZSBhIGhvdXNlaG9sZD8KClJlYXNvbmFibHksIGEgeWVhcidzIHdvcnRoIG9mIGRhdGEgaXMgc3VmZmljaWVudCBmb3IgbW9kZWxsaW5nIHdlYXRoZXIgYW5kIGVuZXJneSBpbnRlcmFjdGlvbnMuLi4KCmBgYHtyfQpoaG9sZHNfbHQxeSA8LSBkYWlseV9lbmVyZ3lfcHJvY2Vzc2VkICU+JSAKICBncm91cF9ieShsY19saWQpICU+JSAKICBzdW1tYXJpc2UoY291bnQgPSBuKCkpICU+JSAKICBmaWx0ZXIoY291bnQgPCAzNjUpICU+JSAKICBwdWxsKGxjX2xpZCkKCmhob2xkc19sdDF5CmBgYAoKMTgyIGhvdXNlaG9sZHMgaGF2ZSBsZXNzIHRoYW4gMzY1IGRheXMnIHdvcnRoIG9mIGRhdGEsIGluIHRoZSA4MjktZGF5IHBlcmlvZC4KCmBgYHtyfQpkYWlseV9lbmVyZ3lfcHJvY2Vzc2VkICU+JSAKICBmaWx0ZXIoIWxjX2xpZCAlaW4lIGhob2xkc19sdDF5KSAlPiUgCiAgZmlsdGVyKGt3aCA9PSAwKSAlPiUgICMgMTQsMDE5IHplcm8ga1doIHZhbHVlcwogIHN1bW1hcmlzZShudW1femVyb3MgPSBuKCksIC5ieSA9IGxjX2xpZCkgJT4lIAogIGFycmFuZ2UoZGVzYyhudW1femVyb3MpKSAjIDI0NiBoaG9sZHMgaGF2ZSB6ZXJvIHZhbHVlcwpgYGAKClJlcGVhdCB0aGUgY2xlYW5pbmcgc3RlcHMgYWJvdmUKCmBgYHtyfQpoaG9sZHNfemVyb192YWx1ZXMyIDwtIGRhaWx5X2VuZXJneV9wcm9jZXNzZWQgJT4lIAogIGZpbHRlcighbGNfbGlkICVpbiUgaGhvbGRzX2x0MXkpICU+JSAKICBmaWx0ZXIoa3doID09IDApICU+JSAgIyAxNCwwMTkgemVybyBrV2ggdmFsdWVzCiAgc3VtbWFyaXNlKG51bV96ZXJvcyA9IG4oKSwgLmJ5ID0gbGNfbGlkKSAlPiUgCiAgYXJyYW5nZShkZXNjKG51bV96ZXJvcykpCgpoaG9sZHNfemVyb192YWx1ZXMyCmBgYAoKMzA4IGhvdXNlaG9sZHMgd2l0aCAwIHZhbHVlcywgMjQ2IGlmIHJlbW92ZSB0aGUgaGhvbGRzIHdpdGggbGVzcyB0aGFuIDM2NSBkYXlzJyBkYXRhCgpgYGB7cn0KaGhvbGRzX3plcm9fdmFsdWVzMiAlPiUgCiAgZmlsdGVyKG51bV96ZXJvcyA8PSAzMCkKYGBgCgoxNjQgaGhvbGRzIGhhdmUgMzAgb3IgZmV3ZXIgemVybyB2YWx1ZXMKCmBgYHtyfQp0b3RhbF9kYXlzID0gYXMubnVtZXJpYyhtYXgoZGFpbHlfZW5lcmd5JGRhdGUpIC0gbWluKGRhaWx5X2VuZXJneSRkYXRlKSArIDEpCgpoaG9sZF9zdGF0cyA8LSBkYWlseV9lbmVyZ3lfcHJvY2Vzc2VkICU+JSAKICBtdXRhdGUoemVyb19rd2ggPSBpZl9lbHNlKGt3aCA9PSAwLCBULCBGKSkgJT4lIAogIGdyb3VwX2J5KGxjX2xpZCkgJT4lCiAgc3VtbWFyaXNlKG51bV92YWx1ZXMgPSBuKCksCiAgICAgICAgICAgIG51bV96ZXJvcyA9IHN1bSh6ZXJvX2t3aCksCiAgICAgICAgICAgIG1pbl92YWx1ZSA9IG1pbihrd2gpLAogICAgICAgICAgICBtYXhfdmFsdWUgPSBtYXgoa3doKSwKICAgICAgICAgICAgcmFuZ2UgPSByYW5nZShrd2gpLAogICAgICAgICAgICBtZWRpYW5fa3doID0gbWVkaWFuKGt3aCksCiAgICAgICAgICAgIGlxciA9IElRUihrd2gpLAogICAgICAgICAgICBtZWFuX2t3aCA9IG1lYW4oa3doKSwKICAgICAgICAgICAgc2QgPSBzZChrd2gpCiAgICAgICAgICAgICkgJT4lIAogIG11dGF0ZShwY19taXNzaW5nID0gKDEwMCAqICh0b3RhbF9kYXlzIC0gbnVtX3ZhbHVlcykgLyB0b3RhbF9kYXlzKSwgLmFmdGVyID0gbGNfbGlkKSAlPiUgCiAgbXV0YXRlKHBjX3plcm9zID0gKDEwMCAqIG51bV96ZXJvcyAvIG51bV92YWx1ZXMpLCAuYWZ0ZXIgPSBwY19taXNzaW5nKSAlPiUgCiAgdW5ncm91cCgpCgpoaG9sZF9zdGF0cyAlPiUgCiAgYXJyYW5nZShkZXNjKHBjX21pc3NpbmcpKQogIApoaG9sZF9zdGF0cyAlPiUgCiAgYXJyYW5nZShkZXNjKHBjX3plcm9zKSkKYGBgCmBgYHtyfQpoaG9sZF9zdGF0cyAlPiUgCiAgZ2dwbG90KCkgKwogIGdlb21fcG9pbnQoYWVzKHggPSBwY19taXNzaW5nLCB5ID0gcGNfemVyb3MpKQpgYGAKCmBgYHtyfQpoaG9sZF9zdGF0cyAlPiUgCiAgZ2dwbG90KCkgKwogIGdlb21faGlzdG9ncmFtKGFlcyh4ID0gcGNfbWlzc2luZyksIGJpbnMgPSAxMDApCmBgYAoKYGBge3J9Cmhob2xkX3N0YXRzICU+JSAKICBnZ3Bsb3QoKSArCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHggPSBwY196ZXJvcyksIGJpbnMgPSAxMDApCmBgYAoKYGBge3J9Cmhob2xkX3N0YXRzICU+JSAKICBmaWx0ZXIocGNfemVyb3MgPT0gMTAwKSAjIDEyIGhvdXNlaG9sZHMgd2l0aCBvbmx5IHplcm8gdmFsdWVzLCByZW1vdmUgdGhlc2UKYGBgCgpgYGB7cn0KaGhvbGRfc3RhdHMgJT4lIAogIGZpbHRlcihwY196ZXJvcyA+IDEwKQpgYGAKCjE0NiBob3VzZWhvbGRzIHdpdGggPiAxMCUgdmFsdWVzIGFzIDAga1doCgowIGtXaCBjb3VsZCBiZSByZWFsLCBlLmcuIG9uIGhvbGlkYXkvb3V0IG9mIGhvdXNlIGFuZCBldmVyeXRoaW5nIHN3aXRjaGVkIG9mZiwgb3IgZXZlbiBpZiBoYXZlIHNvbGFyIHBhbmVscyBhbmQgc3dpdGNoIHRvIG9ubHkgdXNpbmcgdGhhdCBvbiBzdW5ueSBkYXlzIChjb3VsZCBsb29rIGF0IGhpZ2ggc3VuIGxvdyBlbmVyZ3kgZGF5cykKCkJ1dCBvdmVyYWxsLCBsb3RzIG9mIDAgdmFsdWVzIGlzIG5vdCB1c2VmdWwgZm9yIG1vZGVsbGluZyBjaGFuZ2UgaW4gZW5lcmd5IHVzYWdlLCBtb3N0IGhvdXNlaG9sZHMgZG9uJ3QgaGF2ZSBzbyBtYW55IHplcm9lcyAtIHNvIGp1c3QgcmVtb3ZlIHRoZXNlIGhvdXNlaG9sZHMsIGNhbid0IGV4cGxhaW4gdGhlbSByaWdodCBub3cKCmBgYHtyfQpoaG9sZHNfMTBwY196ZXJvIDwtIGhob2xkX3N0YXRzICU+JSAKICBmaWx0ZXIocGNfemVyb3MgPiAxMCkgJT4lIAogIHB1bGwobGNfbGlkKQoKaGhvbGRzXzEwcGNfemVybwpgYGAKCmBgYHtyfQpoaG9sZF9zdGF0cyAlPiUgCiAgZmlsdGVyKCFsY19saWQgJWluJSBoaG9sZHNfMTBwY196ZXJvKSAlPiUgCiAgZmlsdGVyKG1lZGlhbl9rd2ggPT0gMCkKYGBgCgozNiBob3VzZWhvbGRzIHdpdGggbWVkaWFuIG9mIDAgLS0+IGluZGljYXRlcyBub3QgdXNlZnVsIGRhdGE/Ck5vbmUgb2YgdGhlc2Ugb25jZSB0aGUgPjEwcGMgemVybyBoaG9sZHMgcmVtb3ZlZAoKYGBge3J9Cmhob2xkc18xODBvYnMgPC0gaGhvbGRfc3RhdHMgJT4lIAogIGZpbHRlcighbGNfbGlkICVpbiUgaGhvbGRzXzEwcGNfemVybykgJT4lIAogIGZpbHRlcihudW1fdmFsdWVzIDwgMTgwKSAlPiUgCiAgcHVsbChsY19saWQpCgpoaG9sZHNfMTgwb2JzCmBgYAozOCBtb3JlIGhvdXNlaG9sZHMgaGF2ZSBmZXdlciB0aGFuIDE4MCBkYXlzJyB3b3J0aCBvZiBkYXRhLCByZW1vdmUgdGhlc2UuCkNhbid0IGd1YXJhbnRlZSB0aGVzZSBhcmUgY29uc2VjdXRpdmUgZGF5cywgYnV0IDYgbW9udGhzJyB3b3J0aCBvZiBkYXRhIHNob3VsZCBnaXZlIGVub3VnaCB2YXJpYWJpbGl0eSBpbiB3ZWF0aGVyLgpJbWFnaW5lIG1ha2luZyBhIG1vZGVsIHBlciBob3VzZWhvbGQgLSAxODAgdmFsdWVzIHdvdWxkIGJlIGVub3VnaCBJIHRoaW5rLgoKQ2xlYW5pbmcgaG91c2Vob2xkcyBkYXRhOgoKKiB6ZXJvIHZhbHVlczoKICAqICoqcmVtb3ZlIDE0NiBoaG9sZHMgd2l0aCA+MTAlIHplcm8gdmFsdWVzKioKICAqIHdoYXQgZWxzZSB3b3VsZCBzZWVtIHJlYWwgdmVyc3VzIHVudXN1YWw/IGNvdWxkIGFkZCBhIGZhY3RvciB0aGF0IGlzIGFuIGluZGljYXRvciBvZiBudW1femVyb3MsIGluIGNhc2UgbmVlZCB0byByZW1vdmUgYSBncm91cAoqIG51bWJlciBvZiB2YWx1ZXMgKGkuZS4gbWlzc2luZyBkYXRhKTogCiAgICogcmVtb3ZlIHRob3NlIGJlbG93IGEgdGhyZXNob2xkOiBob3cgbXVjaCBkbyB3ZSBuZWVkIGluIG9yZGVyIHRvIHN0YXJ0IGNoYXJhY3RlcmlzaW5nIGEgaG91c2Vob2xkLCBtYXliZSAxODAgZGF5cyAoNiBtb250aHMpIHdvcnRoPyAqKnJlbW92ZSBob3VzZWhvbGRzIHdpdGggbGVzcyB0aGFuIDE4MCBvYnNlcnZhdGlvbnMqKgogICogZm9yIHJlbWFpbmRlcjogY291bGQgYWxzbyBhZGQgYSBmYWN0b3IgdGhhdCBpcyBpbmRpY2F0b3Igb2YgYW1vdW50IG9mIGRhdGEgZm9yIHRoYXQgaG91c2Vob2xkLCBpbiBjYXNlIG5lZWQgdG8gZmFjdG9yIGluIHJlbGlhYmlsaXR5IG9yIGNvdmVyYWdlCiAgCiAgCiMjIyBjbGVhbmluZyBmb3Igc3BlY2lmaWMgZGF0ZXMKCjIwMTQtMDItMjggaGFzIHYgbG93IG1lZGlhbiB2YWx1ZSAoYXQgZW5kIG9mIHRzKSAtIHVudXNhbCwgcmVtb3ZlCmBgYHtyfQpkYWlseV9lbmVyZ3kgJT4lIAogIGZpbHRlcihkYXRlID09ICIyMDE0LTAyLTI4IikgIyA0LDk4NyBvYnMsIHNvIG1vc3QgaG91c2Vob2xkcwpgYGAKCmBgYHtyfQpkYWlseV9lbmVyZ3kgJT4lIAogIGZpbHRlcihkYXRlID09ICIyMDExLTExLTIzIikgIyBzdGFydF9kYXRlCmBgYApTdGFydCBkYXRlIGhhcyBvbmx5IDEzIGhvdXNlaG9sZHMKCkNvbnNpZGVyIGNsZWFuaW5nIGZvciBkYXRlcyB0aGF0IGhhdmUgbWluaW11bSA1MCBob3VzZWhvbGRzPwoKYGBge3J9Cm51bV9oaG9sZHNfYnlfZGF0ZSA8LSBkYWlseV9lbmVyZ3kgJT4lIAogIGdyb3VwX2J5KGRhdGUpICU+JSAKICBzdW1tYXJpc2UobnVtX2hob2xkcyA9IG4oKSkKbnVtX2hob2xkc19ieV9kYXRlCmBgYAoKYGBge3J9Cm51bV9oaG9sZHNfYnlfZGF0ZSAlPiUgCiAgZ2dwbG90KCkgKwogIGdlb21fbGluZShhZXMoeCA9IGRhdGUsIHkgPSBudW1faGhvbGRzKSkKYGBgCkNoZWNrIGZvciBjbGVhbmVkIGRhdGUgdGhhdCByZW1vdmVkIGhvdXNlaG9sZHMgd2l0aCBsaXR0bGUgZGF0YToKCmBgYHtyfQpudW1faGhvbGRzX2J5X2RhdGVfY2xlYW4gPC0gZGFpbHlfZW5lcmd5X2NsZWFuICU+JSAKICBncm91cF9ieShkYXRlKSAlPiUgCiAgc3VtbWFyaXNlKG51bV9oaG9sZHMgPSBuKCkpCm51bV9oaG9sZHNfYnlfZGF0ZV9jbGVhbgpgYGAKCmBgYHtyfQpudW1faGhvbGRzX2J5X2RhdGVfY2xlYW4gJT4lIAogIGdncGxvdCgpICsKICBnZW9tX2xpbmUoYWVzKHggPSBkYXRlLCB5ID0gbnVtX2hob2xkcykpCmBgYAoKV2hhdCBpcyBtaW5pbWFsIG51bWJlciBvZiBob3VzZWhvbGRzIHRvIHRha2UgYSBtZWRpYW4gZnJvbT8gMjAwPwoKVGhhdCB3b3VsZCByZW1vdmUgMTkgZGF0ZXMgKGkuZS4gc3RhcnQgZnJvbSAyMDExLTEyLTEyKQoKYGBge3J9Cm51bV9oaG9sZHNfYnlfZGF0ZV9jbGVhbiAlPiUgCiAgZmlsdGVyKG51bV9oaG9sZHMgPDIwMCkKYGBgCgpPciBpZiBzdGFydCBmcm9tIDIwMTEtMTItMDE6CgpgYGB7cn0KbnVtX2hob2xkc19ieV9kYXRlX2NsZWFuICU+JSAKICBmaWx0ZXIoZGF0ZSA+PSAiMjAxMS0xMi0wMSIpICU+JSAKICBhcnJhbmdlKG51bV9oaG9sZHMpCmBgYAoKTG93ZXN0IHZhbHVlIG9uIGEgc2luZ2xlIGRheSB3b3VsZCBiZSA5MSBob3VzZWhvbGRzLiBUaGF0IHNlZW1zIG9rLgoKU3RhcnQgZnJvbSAyMDExLTEyLTAxIC0gYW5kIGFsc28gZG8gdGhpcyBmb3Igd2VhdGhlcl9kYXRhLgoKCiMjIGhvdXNlaG9sZCBlbmVyZ3kgY2xlYW5pbmcKClJlbWVtYmVyIG90aGVyIHByb2Nlc3NpbmcgSSBjb3VsZCBhZGQgaW46CgoqIGBkYWlseV9lbmVyZ3lfdHJpbWAgY29kZSBjaHVuayBoYXMgc3RlcHMgdG8gYWRkIGluIGRhdGUgdmFsdWVzIGxpa2Ugd2Vla2RheSwgc2Vhc29uLCBldGMgLSB1c2VmdWwgZm9yIG1vZGVsIHRvby4KKiBgZGFpbHlfZW5lcmd5X3Byb2Nlc3NlZGAgaGFzIGNvbXB1dGVkIGFsbHRpbWUgbWVkaWFucyBldGMgZm9yIGNvbXBhcmlzb24KKiBgZGFpbHlfZW5lcmd5X2ltcHV0ZWRgIGhhcyByZW1haW5pbmcgemVyb3MgaW1wdXRlZCB1c2luZyBtZWRpYW4gdmFsdWVzIGZvciB0aGF0IHNlYXNvbiBhbmQgd2Vla2RheSB0eXBlIC0tIGlmIHplcm9lcyBzZWVtIHVucmVhbAoqIGBkYWlseV9lbmVyZ3lfdHNgIC0gaXMgYSB0c2liYmxlIHZlcnNpb24KKiBgZGFpbHlfZW5lcmd5X3JvbGxpbmdfbWVkaWFuYCAtIGhhcyAxNC1kYXkgbW92aW5nIGF2ZXJhZ2UgZm9yIHBsb3R0aW5nIGFzIHRpbWVzZXJpZXMKCgpgYGB7cn0KZGFpbHlfZW5lcmd5X2NsZWFuIDwtIGRhaWx5X2VuZXJneSAlPiUgCiAgZmlsdGVyKCFsY19saWQgJWluJSBoaG9sZHNfMTBwY196ZXJvKSAlPiUgCiAgZmlsdGVyKCFsY19saWQgJWluJSBoaG9sZHNfMTgwb2JzKSAlPiUgCiAgZmlsdGVyKGRhdGUgPj0gIjIwMTEtMTItMDEiICYgZGF0ZSA8ICIyMDE0LTAyLTI4IikKICAjIHN0YXJ0IGluIFdpbnRlciAyMDExICg5MSBob3VzZWhvbGRzIGJ5IHRoYXQgcG9pbnQpCiAgIyBhbmQgcmVtb3ZlIGxhc3QgZGF0ZSwgdmVyeSBsb3cgbWVkaWFuIHZhbHVlICh1bnVzdWFsKQoKbnVtX2hob2xkc19vZyA8LSBsZW5ndGgodW5pcXVlKGRhaWx5X2VuZXJneSRsY19saWQpKQpudW1faGhvbGRzX2NsZWFuIDwtIGxlbmd0aCh1bmlxdWUoZGFpbHlfZW5lcmd5X2NsZWFuJGxjX2xpZCkpCgpudW1faGhvbGRzX29nCm51bV9oaG9sZHNfY2xlYW4KbnVtX2hob2xkc19vZyAtIG51bV9oaG9sZHNfY2xlYW4KMTAwKiAobnVtX2hob2xkc19vZyAtIG51bV9oaG9sZHNfY2xlYW4pIC8gbnVtX2hob2xkc19vZwpgYGAKCjkyIGhvdXNlaG9sZHMgKDEuNiUpIHJlbW92ZWQgZnJvbSBkYXRhLCBsZWF2aW5nIDUsNDc0IGhvdXNlaG9sZHMgaW4gImNsZWFuZWQiIGRhdGEuCgojIyMjIEhvdXNlaG9sZCBzdGF0cwoKIyMjIyMgcmUtcnVuIGhvdXNlaG9sZCBzdW1tYXJ5IHN0YXRzIG9uIGNsZWFuCgpgYGB7cn0KdG90YWxfZGF5cyA9IGFzLm51bWVyaWMobWF4KGRhaWx5X2VuZXJneSRkYXRlKSAtIG1pbihkYWlseV9lbmVyZ3kkZGF0ZSkgKyAxKQoKaGhvbGRfc3RhdHMgPC0gZGFpbHlfZW5lcmd5X2NsZWFuICU+JSAKICBtdXRhdGUoemVyb19rd2ggPSBpZl9lbHNlKGt3aCA9PSAwLCBULCBGKSkgJT4lIAogIGdyb3VwX2J5KGxjX2xpZCkgJT4lCiAgc3VtbWFyaXNlKG51bV92YWx1ZXMgPSBuKCksCiAgICAgICAgICAgIG51bV96ZXJvcyA9IHN1bSh6ZXJvX2t3aCksCiAgICAgICAgICAgIG1pbl92YWx1ZSA9IG1pbihrd2gpLAogICAgICAgICAgICBtYXhfdmFsdWUgPSBtYXgoa3doKSwKICAgICAgICAgICAgcmFuZ2UgPSByYW5nZShrd2gpLAogICAgICAgICAgICBtZWRpYW5fa3doID0gbWVkaWFuKGt3aCksCiAgICAgICAgICAgIGlxciA9IElRUihrd2gpLAogICAgICAgICAgICBtZWFuX2t3aCA9IG1lYW4oa3doKSwKICAgICAgICAgICAgc2QgPSBzZChrd2gpCiAgICAgICAgICAgICkgJT4lIAogIG11dGF0ZShwY19taXNzaW5nID0gKDEwMCAqICh0b3RhbF9kYXlzIC0gbnVtX3ZhbHVlcykgLyB0b3RhbF9kYXlzKSwgLmFmdGVyID0gbGNfbGlkKSAlPiUgCiAgbXV0YXRlKHBjX3plcm9zID0gKDEwMCAqIG51bV96ZXJvcyAvIG51bV92YWx1ZXMpLCAuYWZ0ZXIgPSBwY19taXNzaW5nKSAlPiUgCiAgdW5ncm91cCgpCgpoaG9sZF9zdGF0cyAlPiUgCiAgYXJyYW5nZShkZXNjKHBjX21pc3NpbmcpKQogIApoaG9sZF9zdGF0cyAlPiUgCiAgYXJyYW5nZShkZXNjKHBjX3plcm9zKSkKYGBgCgpgYGB7cn0KaGhvbGRfc3RhdHMgJT4lIAogIGdncGxvdCgpICsgCiAgZ2VvbV9wb2ludChhZXMoeCA9IHBjX21pc3NpbmcsIHkgPSBwY196ZXJvcykpCmBgYAoKYGBge3J9Cmhob2xkX3N0YXRzICU+JSAKICBnZ3Bsb3QoKSArCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHggPSBwY19taXNzaW5nKSwgYmlucyA9IDEwMCkKYGBgCgpgYGB7cn0KaGhvbGRfc3RhdHMgJT4lIAogIGdncGxvdCgpICsKICBnZW9tX2hpc3RvZ3JhbShhZXMoeCA9IHBjX3plcm9zKSwgYmlucyA9IDEwMCkKYGBgCgoKIyMjIyMgbG9vayBhdCBoaG9sZCBzdGF0cwoKYGBge3J9CiMgbWVkaWFuIGJ5IHJhbmdlIC0gcHJvZmlsZSBvZiB2YXJpYW5jZQpoaG9sZF9zdGF0cyAlPiUgCiAgZmlsdGVyKG1lZGlhbl9rd2ggIT0gMCkgJT4lIAogIGdncGxvdCgpICsKICBnZW9tX3BvaW50KGFlcyh4ID0gbWVkaWFuX2t3aCwgeSA9IGlxcikpCmBgYAoKYGBge3J9Cmhob2xkX3N0YXRzICU+JSAKICBmaWx0ZXIobWVkaWFuX2t3aCAhPSAwKSAlPiUgCiAgZ2dwbG90KCkgKwogIGdlb21fcG9pbnQoYWVzKHggPSBtYXhfdmFsdWUsIHkgPSBtZWRpYW5fa3doKSkKYGBgCgpEb2Vzbid0IGxvb2sgbGlrZSBncm91cHMKCmBgYHtyfQpoaG9sZF9zdGF0cyAlPiUgCiAgZmlsdGVyKG1lZGlhbl9rd2ggIT0gMCkgJT4lIAogIGdncGxvdCgpICsKICBnZW9tX3BvaW50KGFlcyh4ID0gbWF4X3ZhbHVlLCB5ID0gbWluX3ZhbHVlKSkKYGBgCkRvZXMgbG9vayBsaWtlIHRoZXJlIG1heSBiZSBjb3JyZWxhdGlvbnMsIGFuZCBtYXliZSBzb21lIGdyb3VwcyBjb3VsZCBiZSBtYWRlIGhlcmUKCiogbG93IG1pbiAmIG1heAoqIGxvdyBtaW4gJiBoaWdoZXIgbWF4IChlLmcuID4gMTAwKQoqIH5+bG93IG1heCAmIGhpZ2hlciBtaW4gKGUuZy4gPiAyKX5+IG5vdCB0aGlzIG9uZQoqIGhpZ2hlciBtaW4gJiBtYXgKCl90b2RvXyBjb3VsZCBsb29rIGF0IHJhdGlvIG9mIG1heC9taW4gYXMgZGVsdGFfY2hhbmdlCgpgYGB7cn0KIyBjaGVjayBmb3IgbWluIG9mIDAKaGhvbGRfc3RhdHMgJT4lIAogIGZpbHRlcihtaW5fdmFsdWUgPT0gMCkgIyA0NTQgcm93cwpgYGAKCmBgYHtyfQojIGNoZWNrIGZvciBtYXggb2YgMApoaG9sZF9zdGF0cyAlPiUgCiAgZmlsdGVyKG1heF92YWx1ZSA9PSAwKQojIDAgaG91c2Vob2xkcwpgYGAKCkNhbGN1bGF0ZSBjaGFuZ2UgYXMgKG1heC1taW4pIG9yIHJhbmdlIC8gbWF4IChiZWNhdXNlIG1heCAhPSAwKQoKYGBge3J9Cmhob2xkX3N0YXRzICU+JSAKICBtdXRhdGUoZGVsdGEgPSByYW5nZSAvIG1heF92YWx1ZSkgJT4lIAogIGdncGxvdCgpICsKICBnZW9tX3BvaW50KGFlcyh4ID0gbWF4X3ZhbHVlLCB5ID0gZGVsdGEpKQpgYGAKCnRoZXJlIGlzIGEgc2hhcGUgaGVyZSAtIGxvdyBtYXhfdmFsdWUgaG91c2Vob2xkcyBoYXZlIGhpZ2hlciBkZWx0YSAoaS5lLiBtb3JlIHJlYWN0aXZlL2NoYW5nZWFibGUpCgpgYGB7cn0KaGhvbGRfc3RhdHMgJT4lIAogIGZpbHRlcihtaW5fdmFsdWUgIT0gMCkKYGBgCgpObywgMTAsNTQ2IHN0aWxsIGhhdmUgbWluIG9mIDAgc28gdGhlaXIgcmFuZ2UvbWF4IHdpbGwgYmUgMQoKVHJ5IGEgbWVhc3VyZSBvZiBjaGFuZ2UgYmVpbmcgSVFSIC8gbWF4CgpgYGB7cn0KaGhvbGRfc3RhdHMgJT4lIAogIG11dGF0ZShkZWx0YSA9IGlxciAvIG1heF92YWx1ZSkgJT4lIAogIGdncGxvdCgpICsKICBnZW9tX3BvaW50KGFlcyh4ID0gbWF4X3ZhbHVlLCB5ID0gZGVsdGEpKQpgYGAKCkNvdWxkIGJlIHNvbWUgZ3JvdXBzIGhlcmUsIGJleW9uZCB0aGUgbWFpbiBjbHVzdGVyIC0gdGhlcmUgYXJlIGhob2xkcyB3aXRoIGhpZ2ggbWF4IHZhbHVlcyBhbmQgbG93IGRlbHRhLCBzdWdnZXN0aW5nIHRoYXQgdGhleSByZW1haW4gaGlnaCBlbmVyZ3kgdXNlcnMgcmVnYXJkbGVzcyAoaS5lLiBub3QgbXVjaCBjaGFuZ2UpCgpvdGhlcnMgYXJlIGxvdyBtYXggdmFsdWUgYW5kIGhpZ2hlciBkZWx0YSwgaW5kaWNhdGluZyB0aGV5IGFyZSBtb3JlIHJhY3RpdmUvcmVzcG9uc2l2ZSBlbmVyZ3kgY29uc3VtZXJzCgpgYGB7cn0KaGhvbGRfc3RhdHMgJT4lIAogIG11dGF0ZShkZWx0YSA9IGlxciAvIG1heF92YWx1ZSkgJT4lIAogIGdncGxvdCgpICsKICBnZW9tX3BvaW50KGFlcyh4ID0gbWluX3ZhbHVlLCB5ID0gZGVsdGEpKQpgYGAKClBsb3R0aW5nIHYgbWluX3ZhbHVlIGlzIGxlc3MgaW5mb3JtYXRpdmUsIG1hbnkgaG91c2Vob2xkcyBoYXZlIGxvdyBtaW5fdmFsdWUuIE5vIHJlYWwgcGF0dGVybiBpbiBkZWx0YSBmb3IgaG91c2Vob2xkcyB3aXRoIGhpZ2hlciBtaW5fdmFsdWUgLSBtYXliZSBhIHBvc2l0aXZlIGNvcnJlbGF0aW9uIHRob3VnaD8KCklkZWE6Ciogc3BsaXQgZW5lcmd5IGRhdGEgZm9yIHdlZWtlbmQgdiB3ZWVrZGF5IGJlZm9yZSBhbmFseXNpbmcgbW9yZQoKIyMgRW5lcmd5IHVzZSB3ZWVrZW5kIHYgd2Vla2RheQoKX0F1ZyAyM18KCldlIGtub3cgc2Vhc29uIGhhcyBhbiBlZmZlY3QsIHNlZSBhYm92ZSB0aW1lIHNlcmllcwoKSW4gbG9va2luZyBhdCB0aGUgc2Vhc29uYWwgZGVjb21wb3NpdGlvbiwgd2UgY2FuIHNlZSB3ZWVrZGF5cyBhcmUgbG93ZXIgdXNhZ2UgdGhhbiB3ZWVrZW5kcwoKU3BsaXQgc3VtbWVyIHYgd2ludGVyLCBhbmQgdGhlbiBzcGxpdCB3ZWVrZGF5IHYgd2Vla2VuZCAtLT4gdml6ICYgc3RhdHMKCiMjIyBTZWFzb25hbGl0eSAKCl9BdWcgMjNfCgpMb29rIGF0IHNlYXNvbmFsaXR5IGFnYWluCgpGaXJzdCBuZWVkIHRvIGNvbnZlcnQgZGF0YSB0byB0c2liYmxlOgoKYGBge3J9CmxpYnJhcnkodHNpYmJsZSkKbGlicmFyeShzbGlkZXIpCmBgYAoKYGBge3J9CiMgY29udmVydCBkYXRhIHRvIHRzaWJibGUKZGFpbHlfZW5lcmd5X2NsZWFuX3RzIDwtIGFzX3RzaWJibGUoZGFpbHlfZW5lcmd5X2NsZWFuLCBrZXkgPSBsY19saWQsIGluZGV4ID0gZGF0ZSkKYGBgCgpgYGB7cn0KaW5kZXhfdmFyKGRhaWx5X2VuZXJneV90cykKYGBgCgpgYGB7cn0Ka2V5X3ZhcnMoZGFpbHlfZW5lcmd5X3RzKQpgYGAKClRvIG1ha2Ugcm9sbGluZyBhdmVyYWdlIGZvciB3aG9sZSBkYXRhc2V0LCBmaXJzdCBmaW5kIG1lZGlhbiBvZiBhbGwgdmFsdWVzIG9uIGEgZWFjaCBkYXRlIChiZWNhdXNlIHZhbHVlcyBhcmUgbm90IG5vcm1hbGx5IGRpc3RyaWJ1dGVkIG9uIGEgc2luZ2xlIGRhdGUgaW4gRGVjZW1iZXIsIHNlZSBhYm92ZSwgc2tld2VkKSwgdGhlbiBkbyBtZWFuIGFjcm9zcyBhIDE0LWRheSB3aW5kb3cgKHNldCBhcyA3IGJlZm9yZSwgNiBhZnRlciwgb25seSBzaG93IGZvciBjb21wbGV0ZSByYW5nZSkuCgpgYGB7cn0KIyBtYWtlIGRmIHdpdGggbWVkaWFuIGZvciBlYWNoIGRheQptZWRpYW5fMTRkX3JvbGxpbmdfZW5lcmd5X2NsZWFuX3RzIDwtIGRhaWx5X2VuZXJneV9jbGVhbl90cyAlPiUgCiAgaW5kZXhfYnkoZGF0ZSkgJT4lIAogIHN1bW1hcmlzZShtZWRpYW5fa3doID0gbWVkaWFuKGt3aCkpICU+JQogICMgYWRkIG1vdmluZ19hdmcgdXNpbmcgdGhlIG1lYW4KICBtdXRhdGUoa3doX21vdmluZ19hdmcgPSBzbGlkZV9kYmwoCiAgICAueCA9IG1lZGlhbl9rd2gsCiAgICAuZiA9IH4gbWVhbiguKSwKICAgIC5iZWZvcmUgPSA3LAogICAgLmFmdGVyID0gNiwgIyAxNC1kYXkgcm9sbGluZyBhdmVyYWdlCiAgICAuY29tcGxldGUgPSBUUlVFCiAgKSkKYGBgCgpgYGB7cn0KbWVkaWFuXzE0ZF9yb2xsaW5nX2VuZXJneV9jbGVhbl90cyAlPiUgCiAgZ2dwbG90KCkgKyAKICBnZW9tX2xpbmUoYWVzKHggPSBkYXRlLCB5ID0gbWVkaWFuX2t3aCksIGNvbG91ciA9ICJncmV5IikgKyAKICBnZW9tX2xpbmUoYWVzKHggPSBkYXRlLCB5ID0ga3doX21vdmluZ19hdmcpLCBjb2xvdXIgPSAicmVkIikgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKClNlYXNvbnMgc3RpbGwgbG9vayBsaWtlIHdpbnRlciBoaWdoLCBzdW1tZXIgbG93LiAKQWxzbyBsb29rcyBsaWtlIGVuZXJneSB1c2FnZSByZWR1Y2luZyB5ZWFyIGJ5IHllYXIKCmBgYHtyfQpsaWJyYXJ5KGZlYXN0cykKYGBgCgpVc2UgdGhlIG1lZGlhbiB2YWx1ZXMKCmBgYHtyfQptZWRpYW5fMTRkX3JvbGxpbmdfZW5lcmd5X2NsZWFuX3RzICU+JSAKICBhdXRvcGxvdChtZWRpYW5fa3doKSArCiAgeGxhYigiRGF0ZSIpICsgeWxhYigiTWVkaWFuIGRhaWx5IGVuZXJneSBjb25zdW1wdGlvbiAoa1doKSIpCmBgYAoKCmBgYHtyfQojIGxvb2sgYXQgc2Vhc29uYWwgcGF0dGVybgptZWRpYW5fMTRkX3JvbGxpbmdfZW5lcmd5X2NsZWFuX3RzICU+JSAKICBnZ19zZWFzb24obWVkaWFuX2t3aCkKYGBgCgpTZWFzb25hbCBwYXR0ZXJuIGlzIGNvbnNpc3RlbnQgeWVhci1vbi15ZWFyLiBNYXliZSAyMDE0IGxvb2tzIGxpa2Ugc2xpZ2h0bHkgbG93ZXIgdXNhZ2U/IEFsc28gbm90ZSBzb21lIGhpZ2hlciB2YXJpYWJpbGl0eSBpbiB3aW50ZXIgMjAxMSBkYXRhLgoKTmVlZCB0byByZW1vdmUgbGFzdCBkYXRlIGJlY2F1c2UgdmFsdWVzIGFyZSBvZmY/IEFuZCBmaXJzdCBkYXRlIHRvbz8gSGF2ZSByZW1vdmVkIGluIGNsZWFuaW5nIHN0ZXAgbm93LgoKIyMjIyMgdHJlbmQ6IHdlZWtkYXlzCgpfQXVnIDIzXwoKTG9vayBhdCBzdWJzZXJpZXM6CgpDYW4gc2VlIHRyZW5kIGZvciBldmVyeSB3ZWVrZGF5IC0gZG9lcyBsb29rIGxpa2UgU2F0ICYgU3VuIGFyZSBoaWdoZXIuCmBgYHtyfQptZWRpYW5fMTRkX3JvbGxpbmdfZW5lcmd5X2NsZWFuX3RzICU+JSAKICBnZ19zdWJzZXJpZXMoeSA9IG1lZGlhbl9rd2gsIHBlcmlvZCA9IDcpCmBgYAoKWWVzLCB3ZWVrZW5kcyBibHVlIGxpbmVzIHN0aWxsIGxvb2sgaGlnaGVyIHRoYW4gZm9yIHdlZWtkYXlzCgojIyMjIHdkYXkgdiBzZWFzb24KCl9BdWcgMjNfCgpTcGxpdCBpbnRvIHdpbnRlciBhbmQgc3VtbWVyIGRhdGEgb25seSBhbmQgcmVwZWF0IHRvIHNlZSBpZiB0aGVyZSBpcyBzZWFzb25hbCBpbnRlcmFjdGlvbiBoZXJlCgpgYGB7cn0KbWVkaWFuXzE0ZF9yb2xsaW5nX2VuZXJneV9jbGVhbl90cwpgYGAKCmBgYHtyfQpzZWFzb25zX2VuZXJneV9jbGVhbl90cyA8LSBkYWlseV9lbmVyZ3lfY2xlYW5fdHMgJT4lIAogIG11dGF0ZSh3ZWVrZGF5ID0gd2RheShkYXRlLCBsYWJlbCA9IFRSVUUpLCAuYWZ0ZXIgPSBkYXRlKSAlPiUgCiAgbXV0YXRlKGlzX3dlZWtlbmQgPSBpZl9lbHNlKHdlZWtkYXkgJWluJSBjKCJTYXQiLCJTdW4iKSwgVCwgRiksIC5hZnRlciA9IHdlZWtkYXkpICU+JQogICMgbm90ZSBkb24ndCB1c2Ugem9vIGZ1bmN0aW9uIG9uIHRzaWJibGUsIHVzZSB5ZWFybW9udGgoKQogIG11dGF0ZSh5ZWFybW9udGggPSB5ZWFybW9udGgoZGF0ZSksIC5hZnRlciA9IGRhdGUpICU+JSAKICBtdXRhdGUobW9udGggPSBtb250aChkYXRlLCBsYWJlbCA9IEZBTFNFKSwgLmFmdGVyID0gZGF0ZSkgJT4lIAogIG11dGF0ZShxdWFydGVyID0gcXVhcnRlcihkYXRlLCB0eXBlID0gImRhdGVfZmlyc3QiLCBmaXNjYWxfc3RhcnQgPSAxMiksIC5hZnRlciA9IGRhdGUpICU+JSAKICBtdXRhdGUoeWVhcnNlYXNvbiA9IGNhc2Vfd2hlbigKICAgICNxdWFydGVyID09ICIyMDExLTA5LTAxIiB+ICJBdXR1bW4gMjAxMSIsCiAgICBxdWFydGVyID09ICIyMDExLTEyLTAxIiB+ICJXaW50ZXIgMjAxMSIsCiAgICBxdWFydGVyID09ICIyMDEyLTAzLTAxIiB+ICJTcHJpbmcgMjAxMiIsCiAgICBxdWFydGVyID09ICIyMDEyLTA2LTAxIiB+ICJTdW1tZXIgMjAxMiIsCiAgICBxdWFydGVyID09ICIyMDEyLTA5LTAxIiB+ICJBdXR1bW4gMjAxMiIsCiAgICBxdWFydGVyID09ICIyMDEyLTEyLTAxIiB+ICJXaW50ZXIgMjAxMiIsCiAgICBxdWFydGVyID09ICIyMDEzLTAzLTAxIiB+ICJTcHJpbmcgMjAxMyIsCiAgICBxdWFydGVyID09ICIyMDEzLTA2LTAxIiB+ICJTdW1tZXIgMjAxMyIsCiAgICBxdWFydGVyID09ICIyMDEzLTA5LTAxIiB+ICJBdXR1bW4gMjAxMyIsCiAgICBxdWFydGVyID09ICIyMDEzLTEyLTAxIiB+ICJXaW50ZXIgMjAxMyIsCiAgICAuZGVmYXVsdCA9IE5BX2NoYXJhY3Rlcl8KICApLCAuYWZ0ZXIgPSBxdWFydGVyKSAlPiUgCiAgbXV0YXRlKHllYXJzZWFzb24gPSBmYWN0b3IoeWVhcnNlYXNvbiwgbGV2ZWxzID0gYygjIkF1dHVtbiAyMDExIiwKICAgICJXaW50ZXIgMjAxMSIsICJTcHJpbmcgMjAxMiIsICJTdW1tZXIgMjAxMiIsCiAgICAiQXV0dW1uIDIwMTIiLCAiV2ludGVyIDIwMTIiLCAiU3ByaW5nIDIwMTMiLCAKICAgICJTdW1tZXIgMjAxMyIsICJBdXR1bW4gMjAxMyIsICJXaW50ZXIgMjAxMyIpKSkKYGBgCgpgYGB7cn0KIyBtYWtlIGEgd2ludGVyIHN1YnNldCAoV2ludGVyIDIwMTEsIDIwMTIsIDIwMTMgLSB0aGUgeWVhciBpcyB3aGVuIERlY2VtYmVyIHdhcyBzbyBXaW50ZXIgMjAxMyA9IERlYyAyMDEzLCBKYW4gKyBGZWIgMjAxNCkKd2ludGVyX2VuZXJneV90cyA8LSBzZWFzb25zX2VuZXJneV9jbGVhbl90cyAlPiUgCiAgZmlsdGVyKHN0cl9kZXRlY3QoeWVhcnNlYXNvbiwgIldpbnRlcioiKSkKYGBgCgpgYGB7cn0KIyBtYWtlIGEgc3VtbWVyIHN1YnNldCAoU3VtbWVyIDIwMTIsIDIwMTMpCnN1bW1lcl9lbmVyZ3lfdHMgPC0gc2Vhc29uc19lbmVyZ3lfY2xlYW5fdHMgJT4lIAogIGZpbHRlcihzdHJfZGV0ZWN0KHllYXJzZWFzb24sICJTdW1tZXIqIikpCmBgYAoKIyMjIyB3aW50ZXIgcm9sbGluZyBhdmcKCmBgYHtyfQojIGFkZCBtZWRpYW5zICYgcm9sbGluZyBhdmcgZm9yIHdpbnRlciBvbmx5CndpbnRlcl9yb2xsaW5nX3RzIDwtIHdpbnRlcl9lbmVyZ3lfdHMgJT4lIAogIGluZGV4X2J5KGRhdGUpICU+JSAKICBzdW1tYXJpc2UobWVkaWFuX2t3aCA9IG1lZGlhbihrd2gpKSAlPiUKICAjIGFkZCBtb3ZpbmdfYXZnIHVzaW5nIHRoZSBtZWFuCiAgbXV0YXRlKGt3aF9tb3ZpbmdfYXZnID0gc2xpZGVfZGJsKAogICAgLnggPSBtZWRpYW5fa3doLAogICAgLmYgPSB+IG1lYW4oLiksCiAgICAuYmVmb3JlID0gNywKICAgIC5hZnRlciA9IDYsICMgMTQtZGF5IHJvbGxpbmcgYXZlcmFnZQogICAgLmNvbXBsZXRlID0gVFJVRQogICkpICU+JSAKICBmaWxsX2dhcHMoKQpgYGAKCmBgYHtyfQp3aW50ZXJfcm9sbGluZ190cyAlPiUgCiAgZ2dwbG90KCkgKyAKICBnZW9tX2xpbmUoYWVzKHggPSBkYXRlLCB5ID0gbWVkaWFuX2t3aCksIGNvbG91ciA9ICJncmV5IikgKyAKICBnZW9tX2xpbmUoYWVzKHggPSBkYXRlLCB5ID0ga3doX21vdmluZ19hdmcpLCBjb2xvdXIgPSAicmVkIikgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKYGBge3J9CndpbnRlcl9yb2xsaW5nX3RzICU+JSAKICBmaWxsX2dhcHMoKSAlPiUgCiAgZ2dfc3Vic2VyaWVzKHkgPSBtZWRpYW5fa3doLCBwZXJpb2QgPSA3KQpgYGAKCmBgYHtyfQp3aW50ZXJfcm9sbGluZ193ZGF5X3RzIDwtIHdpbnRlcl9yb2xsaW5nX3RzICU+JSAKICBtdXRhdGUod2Vla2RheSA9IHdkYXkoZGF0ZSwgbGFiZWwgPSBUUlVFKSwgLmFmdGVyID0gZGF0ZSkgJT4lIAogIG11dGF0ZShpc193ZWVrZW5kID0gaWZfZWxzZSh3ZWVrZGF5ICVpbiUgYygiU2F0IiwiU3VuIiksIFQsIEYpLCAuYWZ0ZXIgPSB3ZWVrZGF5KQoKd2ludGVyX3JvbGxpbmdfd2RheV90cwpgYGAKCmBgYHtyfQp3aW50ZXJfcm9sbGluZ193ZGF5X3RzICU+JSAKICBnZ3Bsb3QoKSArCiAgZ2VvbV9ib3hwbG90KGFlcyh4ID0gaXNfd2Vla2VuZCwgeSA9IG1lZGlhbl9rd2gpKQpgYGAKCldhcm5pbmcgc2F5czogUmVtb3ZlZCA1NTAgcm93cyBjb250YWluaW5nIG5vbi1maW5pdGUgdmFsdWVzCgpgYGB7cn0Kd2ludGVyX3JvbGxpbmdfd2RheV90cyAlPiUgCiAgZmlsdGVyKGlzX3dlZWtlbmQpICU+JSAKICBnZ3Bsb3QoKSArCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHggPSBtZWRpYW5fa3doKSwgYmlucyA9IDUwKQpgYGAKX1dhcm5pbmc6IFJlbW92ZWQgMTU3IHJvd3MgY29udGFpbmluZyBub24tZmluaXRlIHZhbHVlc18KCkxvb2tzIGFwcHJveGltYXRlbHkgbm9ybWFsIChzb21lIG1pc3NpbmcgdmFsdWVzKQoKYGBge3J9CndpbnRlcl9yb2xsaW5nX3dkYXlfdHMgJT4lIAogIGZpbHRlcighaXNfd2Vla2VuZCkgJT4lIAogIGdncGxvdCgpICsKICBnZW9tX2hpc3RvZ3JhbShhZXMoeCA9IG1lZGlhbl9rd2gpLCBiaW5zID0gNTApCmBgYApfV2FybmluZzogUmVtb3ZlZCAzOTMgcm93cyBjb250YWluaW5nIG5vbi1maW5pdGUgdmFsdWVzXwoKTm90IG5vcm1hbCwgaGFzIDEgcm93IHdpdGggdmVyeSBsb3cgbWVkaWFuCgpgYGB7cn0Kd2ludGVyX3JvbGxpbmdfd2RheV90cyAlPiUgCiAgYXJyYW5nZShtZWRpYW5fa3doKQpgYGAKClRoZSBsb3cgdmFsdWUgaXMgdGhlIGxhc3QgZGF0ZSBvZiB0aGUgdHJpYWwgLSBsb29rcyBvZGQgaW4gb3RoZXIgZ3JhcGhzLCBzbyByZW1vdmUgaXQgZnJvbSB0aGUgZGF0YXNldC4gPC0tIG9sZCwgZGVhbHRoIHdpdGggYnkgcmVtb3ZpbmcgMjAxNC0wMi0yOCBpbiBjbGVhbmluZyBzdGVwCgojIyMjIyBUT0RPIERFQUwgV0lUSCBXQVJOSU5HUwoKX3RvZG86IERFQUwgV0lUSCBXQVJOSU5HUyBBQk9WRV8KTm90ZSB0aGV5IHN1bSB0byA1NTAhIAoKIyMjIyMgVE9ETyBhbmFseXNlIHdlZWtlbmQgdiB3ZGF5IGluIHN1bW1lciB0b28KCl9UT0RPXwoKIyMjIyMgVE9ETyBmb3JlY2FzdGluZyBtb2RlbAoKX1RPRE9fCgojIyBEYWlseSB3ZWF0aGVyIGRhdGEKCkRhdGFzZXQgMiwgZnJvbSBodHRwczovL3d3dy5rYWdnbGUuY29tL2RhdGFzZXRzL2VtbWFudWVsZndlcnIvbG9uZG9uLXdlYXRoZXItZGF0YS4KCiMjIyBwcmVwIHdlYXRoZXIgZGF0YQoKX0F1ZyAyM18KCmBgYHtyfQpkYWlseV93ZWF0aGVyIDwtIHJlYWRfY3N2KCIuLi8zX3Jhd19kYXRhL2xvbmRvbl93ZWF0aGVyLmNzdiIpCmBgYApgYGB7cn0Kc2tpbXI6OnNraW0oZGFpbHlfd2VhdGhlcikKYGBgCmRhdGUgaXMgbm90IGRhdGUgdmFsdWUKZGF0ZXMgY292ZXIgMTk3OSB0byAyMDIwIGRlY2VtYmVyLCBmaWx0ZXIgdG8gcmFuZ2Ugb2YgZW5lcmd5IGRhdGEKCmBgYHtyfQpzdGFydF9kYXRlIDwtIG1pbihkYWlseV9lbmVyZ3lfY2xlYW4kZGF0ZSkKc3RhcnRfZGF0ZQoKZW5kX2RhdGUgPC0gbWF4KGRhaWx5X2VuZXJneV9jbGVhbiRkYXRlKQplbmRfZGF0ZQpgYGAKCgpgYGB7cn0KZGFpbHlfd2VhdGhlcl90cmltIDwtIGRhaWx5X3dlYXRoZXIgJT4lIAogIG11dGF0ZShkYXRlID0geW1kKGRhdGUpKSAlPiUgCiAgZmlsdGVyKGRhdGUgPj0gc3RhcnRfZGF0ZSAmIGRhdGUgPD0gZW5kX2RhdGUpCgpza2ltcjo6c2tpbShkYWlseV93ZWF0aGVyX3RyaW0pCmBgYAoKIyMjIGpvaW4gZW5lcmd5ICsgd2VhdGhlciBkYXRhCgpfQXVnIDIzXwoKYGBge3J9CiMgam9pbiBlbmVyZ3kgZGF0YSB3aXRoIHdlYXRoZXIgZGF0YQpkYWlseV9lbmVyZ3lfd2VhdGhlciA8LSBsZWZ0X2pvaW4oeCA9IGRhaWx5X2VuZXJneV9jbGVhbiwgeSA9IGRhaWx5X3dlYXRoZXJfdHJpbSwgYnkgPSBqb2luX2J5KGRhdGUpKQpkYWlseV9lbmVyZ3lfd2VhdGhlcgpgYGAKCgojIyMgbW9kZWwgaGhvbGRzIHdpdGggd2VhdGhlciBhbmQgdGltZQoKX0F1ZyAyM18KCiogaWRlYTogaWRlbnRpZnkgdHlwZXMgb2YgaG91c2Vob2xkIC0gbG9vayB0byBzZWUgaWYgdGhlcmUgYXJlIGFueSBncm91cHMgYnkgcGF0dGVybnMgb2YgY29uc3VtcHRpb24gKG5vdCBqdXN0IG92ZXJhbGwgaGlnaC9sb3cgYnV0IHZhcmlhYmxlIHYgZml4ZWQgb3ZlciB0aW1lKSAtIHRyeSBjbHVzdGVyaW5nPwoKZG8gaG91c2Vob2xkcyByZXNwb25kIGRpZmZlcmVudGx5IHRvIHdlYXRoZXI/IGNhbiB3ZSBpZGVudGlmeSBob3VzZWhvbGRzIHRoYXQgbWlnaHQgbmVlZCB0byBpbXByb3ZlIHRoZWlyIGhlYXRpbmcgKGllIGhpZ2ggZW5lcmd5IHVzYWdlIG9uIGNvbGQgZGF5cykgLSBib3RoIGluIHRlcm1zIG9mIGVuZXJneSBlZmZpY2llbmN5IGFuZCBob21lIGluc3VsYXRpb24KCmZpcnN0LCBjaGVjayBpZiBtZWFuX3RlbXAgYW5kIGt3aCBjb3JyZWxhdGUgKGZvciBoaWdoL2F2ZXJhZ2UvbG93IGNvbnN1bWVyIG1lZGlhbnMpIC0tPiBsb2FkIHdlYXRoZXIgZGF0YQoKClJlbWFpbmluZyBpZGVhczoKCiogdHlwZXMgb2Ygd2VhdGhlciBkYXlzIC0gc2VlIGlmIHRoZXJlIGFyZSBjbHVzdGVycyBvZiB0ZW1wZXJhdHVyZSwgcHJlY2lwaXRhdGlvbiwgY2xvdWQgY292ZXIsIHN1bnNoaW5lIGV0YyAtLT4gY2F0ZWdvcmllcyAibWlsZCBjbG91ZHkgcmFpbnkgZGF5IiBldGMKCl93cml0dGVuIHNjcmlwdHMgdG8gcHJlcGFyZSBkYXRhIChpbmNsdWRpbmcgam9pbmVkIGRhdGEpLCBtb3ZpbmcgdG8gbmV3IG5vdGVib29rIG5vdyB0byBleHBsb3JlIHdlYXRoZXIgZGF0YSBhbmQgaG91c2Vob2xkIGVuZXJneS93ZWF0aGVyIGZlYXR1cmVzXwo=